package com.andr3h3nriqu3s.applications

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include
import java.sql.ResultSet
import java.sql.Timestamp
import java.util.Date
import java.util.UUID
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

enum class EventType(val value: Int) {
    Creation(0),
    StatusUpdate(1),
    View(2)
}

@JsonInclude(Include.NON_NULL)
data class EventStat(var i: String, var a: String, var t: Int, var s: String?, var c: Long)

fun String.decodeHex(): ByteArray {
    check(length % 2 == 0) { "Must have an even length" }

    return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
}

data class Event(
        var id: String,
        var application_id: String,
        var event_type: Int,
        var new_status_id: String?,
        var time: Timestamp
) {
    companion object : RowMapper<Event> {
        override public fun mapRow(rs: ResultSet, rowNum: Int): Event {
            return Event(
                    rs.getString("id"),
                    rs.getString("application_id"),
                    rs.getInt("event_type"),
                    rs.getString("new_status_id"),
                    rs.getTimestamp("time"),
            )
        }
    }
    fun toSimple(): EventStat {
        return EventStat(
                id.replace("-", "").substring(0, 10),
                application_id,
                /*Base64.getEncoder()
                .encodeToString(application_id.replace("-", "").decodeHex())
                .replace("==", ""),*/
                event_type,
                new_status_id,
                /*if (new_status_id == null) null
                else
                        Base64.getEncoder()
                                .encodeToString(
                                        (new_status_id as String).replace("-", "").decodeHex()
                                )
                                .replace("==", ""),*/
                time.time
        )
    }
}

@RestController
@ControllerAdvice
@RequestMapping("/api/events")
class EventController(
        val sessionService: SessionService,
        val applicationService: ApplicationService,
        val eventService: EventService
) {

    @GetMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun getCV(@PathVariable id: String, @RequestHeader("token") token: String): List<Event> {
        val user = sessionService.verifyTokenThrow(token)

        val application = applicationService.findApplicationById(user, id)

        if (application == null) {
            throw NotFound()
        }

        return application.events
    }

    @GetMapping(path = ["/"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun getCV(@RequestHeader("token") token: String): List<EventStat> {
        sessionService.verifyTokenThrow(token)

        return eventService.findAll().map { it.toSimple() }.toList()
    }
}

// Note I decided that events are read+delete only
@Service
public class EventService(val db: JdbcTemplate) {

    public fun listFromApplicationId(id: String): Iterable<Event> =
            db.query(
                    "select * from events where application_id=? order by time asc;",
                    arrayOf(id),
                    Event
            )

    public fun findAll(): Iterable<Event> =
            db.query("select * from events order by time asc;", Event)

    public fun getById(id: String): Event? {
        val items = db.query("select * from events where id=?;", arrayOf(id), Event)
        if (items.size == 0) {
            return null
        }
        return items.first()
    }

    public fun deleteById(id: String): Event {
        val event = this.getById(id)
        if (event == null) {
            throw NotFound()
        }

        db.update("delete from events where id=?", id)

        return event
    }

    public fun create(
            application_id: String,
            event_type: EventType,
            new_status_id: String? = null
    ): Event {
        val id = UUID.randomUUID().toString()

        var new_event =
                Event(
                        id,
                        application_id,
                        event_type.value,
                        new_status_id,
                        Timestamp(Date().getTime())
                )

        db.update(
                "insert into events (id, application_id, event_type, new_status_id) values (?, ?, ? ,?)",
                new_event.id,
                new_event.application_id,
                new_event.event_type,
                new_event.new_status_id,
        )

        return new_event
    }
}
