package com.andr3h3nriqu3s.applications

import io.opentelemetry.api.OpenTelemetry
import java.time.LocalDate
import kotlin.io.println
import org.json.JSONArray
import org.json.JSONObject
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.reactive.function.client.ExchangeStrategies
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.bodyToMono
import reactor.core.publisher.Mono

@RestController
@ControllerAdvice
@RequestMapping("/api/mail")
@kotlin.io.encoding.ExperimentalEncodingApi
class MailController(
        val userService: UserService,
        val applicationService: ApplicationService,
        val sessionService: SessionService,
        val openTelemetry: OpenTelemetry,
) {

    @Value("\${mail.token.api}") private val ApiToken: String? = null
    @Value("\${mail.local.user}") private val TargetUserUUID: String? = null

    private var failed = false
    private var user: UserDb? = null
    var value = 0
    var account: String? = null
    var client: WebClient? = null
    var mailbox: String? = null

    @Scheduled(initialDelay = 15000, fixedDelay = 10 * 60 * 1000)
    public fun getLastEmail(): List<Application> {
        if (failed) {
            error("Get Last Email marked failed skiping")
        }
        if (TargetUserUUID == null || TargetUserUUID == "") {
            error("Target User not defined")
        }
        val tracer = openTelemetry.getTracer("applications")

        if (user == null) {
            user = userService.findUserById(TargetUserUUID)
            if (user == null) {
                failed = true
                error("User ${TargetUserUUID} not found")
            }
        }

        if (client == null) {
            val size = 16 * 1024 * 1024
            val strategies =
                    ExchangeStrategies.builder()
                            .codecs { it.defaultCodecs().maxInMemorySize(size) }
                            .build()
            client =
                    WebClient.builder()
                            .exchangeStrategies(strategies)
                            .baseUrl("https://api.fastmail.com")
                            .defaultHeader("Authorization", "Bearer ${this.ApiToken}")
                            .defaultHeader(
                                    HttpHeaders.CONTENT_TYPE,
                                    MediaType.APPLICATION_JSON_VALUE
                            )
                            .build()
        }
        if (account == null) {
            println("getting account")
            var session_data_r =
                    client!!.get().uri("jmap/session").retrieve().bodyToMono<String>().block()!!
            var session_data = JSONObject(session_data_r)
            account = session_data.getJSONObject("accounts").keys().next()
        }
        if (mailbox == null) {
            val mailbox_r =
                    JMapQueryBuilder("Mailbox/query", "mailboxes", account!!)
                            .filter("name", "Inbox")
            println("getting mailbox ${mailbox_r}")
            var mailbox_r_data =
                    JSONObject(
                            client!!.post()
                                    .uri("jmap/api")
                                    .bodyValue(mailbox_r.toString())
                                    .retrieve()
                                    .bodyToMono<String>()
                                    .block()!!
                    )
            mailbox =
                    mailbox_r_data
                            .getJSONArray("methodResponses")
                            .getJSONArray(0)
                            .getJSONObject(1)
                            .getJSONArray("ids")
                            .getString(0)
            println("mailbox got ${mailbox}")
        }

        val listSpan = tracer.spanBuilder("list email").startSpan()
        println("\n\nGet Email @ ${LocalDate.now()}\n\n")
        val email_get_obj =
                JMapQueryBuilder("Email/query", "list_emails", account!!)
                        .arg("limit", 1)
                        .arg("position", 0)
                        .withOperator("AND")
                        .filter(
                                "_",
                                JMapQueryBuilder()
                                        .filter("inMailbox", mailbox!!)
                                        .filter("notKeyword", "\$seen")
                                        .toFilters()
                        )
                        .filter(
                                "_",
                                JMapQueryBuilder()
                                        .withOperator("OR")
                                        .filter("from", "noreply@glassdoor.com")
                                        .filter("from", "jobalerts-noreply@linkedin.com")
                                        .filter("from", "jobs-listings@linkedin.com")
                                        .toFilters()
                        )
                        .sort("receivedAt", true)
                        .newQuery("Email/get", "email")
                        .arg("bodyProperties", arrayOf("partId", "blobId", "size", "type"))
                        .arg(
                                "properties",
                                arrayOf(
                                        "id",
                                        "from",
                                        "cc",
                                        "subject",
                                        "htmlBody",
                                        "textBody",
                                        "bodyValues",
                                        "keywords"
                                )
                        )
                        .arg("fetchTextBodyValues", true)
                        .arg("fetchHTMLBodyValues", true)
                        .argFromPrev("#ids", "list_emails", "Email/query", "/ids")
                        .print()

        val email_data_r =
                client!!.post()
                        .uri("jmap/api")
                        .bodyValue(email_get_obj.toString())
                        .exchangeToMono<String> {
                            if (it.statusCode().value().equals(200)) {
                                it.bodyToMono<String>()
                            } else {
                                println("here ${it.statusCode()} ${it.statusCode().value()}")
                                Mono.just("asdfasdf")
                            }
                        }
                        .block()!!
        // .bodyToMono<String>()

        val email_data = JSONObject(email_data_r)

        println("GOT email data")

        val email_r =
                (email_data
                                .getJSONArray("methodResponses")
                                .filter { (it as JSONArray).getString(0) == "Email/get" }
                                .get(0) as
                                JSONArray)
                        .getJSONObject(1)

        val listSize: Int = email_r.getJSONArray("list").length()
        if (listSize == 0) {
            println("No emails found")
            listSpan.setAttribute("emailCount", 0)
            listSpan.end()
            return arrayListOf()
        }
        listSpan.setAttribute("emailCount", listSize.toLong())
        listSpan.end()

        val email_html_body = email_r.getJSONArray("list").getJSONObject(0).getJSONArray("htmlBody")

        if (email_html_body.length() != 1) {
            error("Not sure how to handle non 1 sized html blobs")
        }

        val email_html_body_id = email_html_body.getJSONObject(0).getString("partId")

        val email_text_obj =
                email_r.getJSONArray("list")
                        .getJSONObject(0)
                        .getJSONObject("bodyValues")
                        .getJSONObject(email_html_body_id)
                        .getString("value")

        var applications: List<Application> = arrayListOf()

        if (!email_text_obj.contains("Don&#x27;t forget to apply to these jobs") &&
                        !email_text_obj.contains("Your recently viewed jobs")
        ) {
            println("\n\nAdding Emails\n\n")
            applications = applicationService.parseText(user!!, email_text_obj)
            println("\n\nUdating email\n\n")
        } else {
            println("Its one of those awfull glassdoor emails")
        }

        val email_id = email_r.getJSONArray("list").getJSONObject(0).getString("id")

        val setSpan = tracer.spanBuilder("set emails as read").startSpan()
        val update_email =
                JMapQueryBuilder("Email/set", "set", account!!)
                        .updateNewItem(email_id)
                        .update("keywords/\$seen", true)
                        .print()

        client!!.post()
                .uri("jmap/api")
                .bodyValue(update_email.toString())
                .retrieve()
                .bodyToMono<String>()
                .block()!!
        setSpan.end();

        return applications
    }

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

class JMapQueryBuilder {
    var query: String
    var id: String
    val accountId: String

    private var queries: JSONArray? = null
    private var _filters: JSONArray? = null
    private var filters: JSONObject? = null
    private var args: JSONObject = JSONObject()
    private var sorts: JSONArray? = null

    private var update: JSONObject? = null
    private var update_cur: JSONObject? = null

    constructor(query: String, id: String, accountId: String) {
        this.query = query
        this.id = id
        this.accountId = accountId
    }

    constructor() {
        this.query = ""
        this.id = ""
        this.accountId = ""
    }

    fun updateNewItem(id: String): JMapQueryBuilder {
        if (update == null) {
            update = JSONObject()
        }
        update_cur = JSONObject()
        update?.put(id, update_cur)
        return this
    }

    // updateNewItem MUST be called before this
    fun update(key: String, value: Any): JMapQueryBuilder {
        update_cur?.put(key, value)
        return this
    }

    fun newQuery(query: String, id: String): JMapQueryBuilder {
        if (queries == null) {
            queries = JSONArray()
        }
        queries?.put(toQueryOBJ())
        _filters = null
        filters = null
        args = JSONObject()
        sorts = null
        this.query = query
        this.id = id
        return this
    }

    fun withOperator(operator: String): JMapQueryBuilder {
        filters = JSONObject()
        filters?.put("operator", operator)
        _filters = JSONArray()
        filters?.put("conditions", _filters)
        return this
    }

    fun toFilters(): JSONObject {
        return this.filters!!
    }

    fun filter(key: String, value: Any): JMapQueryBuilder {
        if (this._filters != null) {
            if (key == "_") {
                this._filters?.put(value)
            } else {
                var obj = JSONObject()
                obj.put(key, value)
                this._filters?.put(obj)
            }
            return this
        }
        if (this.filters == null) {
            this.filters = JSONObject()
        }
        this.filters?.put(key, value)
        return this
    }

    fun print(): JMapQueryBuilder {
        println(this.toQuery())
        return this
    }

    fun arg(key: String, value: Any): JMapQueryBuilder {
        this.args.put(key, value)
        return this
    }

    fun argFromPrev(
            key: String,
            prevName: String,
            prevQuery: String,
            path: String
    ): JMapQueryBuilder {
        var obj = JSONObject()
        obj.put("resultOf", prevName)
        obj.put("name", prevQuery)
        obj.put("path", path)
        this.args.put(key, obj)
        return this
    }

    fun sort(key: String, isAscending: Boolean): JMapQueryBuilder {
        if (this.sorts == null) {
            this.sorts = JSONArray()
        }
        val obj = JSONObject()
        obj.put("property", key)
        obj.put("isAscending", isAscending)
        this.sorts?.put(obj)
        return this
    }

    fun toQueryOBJ(): JSONArray {
        var query = JSONArray()

        args.put("accountId", accountId)
        if (filters != null) {
            args.put("filter", filters)
        }

        if (sorts != null) {
            args.put("sort", sorts)
        }

        if (update != null) {
            args.put("update", update)
        }

        query.put(this.query)
        query.put(args)
        query.put(id)

        return query
    }

    fun toQuery(): String {
        var rj = JSONObject()
        rj.put("using", arrayListOf("urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"))

        if (queries != null) {
            queries?.put(toQueryOBJ())
            rj.put("methodCalls", queries)
        } else {
            rj.put("methodCalls", arrayOf(toQueryOBJ()))
        }

        return rj.toString()
    }

    override fun toString(): String {
        return toQuery()
    }
}
