package com.correx.apps.server.routes

import com.correx.apps.server.ServerModule
import com.correx.apps.server.metrics.MetricsInspectionService
import com.correx.apps.server.research.approveSourceList
import com.correx.core.events.EventDispatcher
import com.correx.apps.server.protocol.SessionConfigDto
import com.correx.apps.server.replay.ReplayInspectionService
import com.correx.apps.server.serialization.payloadDiscriminator
import com.correx.apps.server.ws.SessionStreamHandler
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.InitialIntentEvent
import com.correx.core.events.serialization.eventJson
import com.correx.core.events.types.SessionId
import com.correx.core.utils.TypeId
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import io.ktor.server.websocket.webSocket
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import org.slf4j.LoggerFactory
import java.util.*

internal const val DEFAULT_EVENT_LIMIT = 200

@Serializable
data class EventRow(
    val sequence: Long,
    val eventId: String,
    val type: String,
    val timestamp: String,
    val causationId: String?,
    val correlationId: String?,
    val payload: JsonElement,
)

@Serializable
data class StartSessionRequest(
    val workflowId: String,
    val config: SessionConfigDto? = null,
    // Optional freeform brief for intent-driven workflows (REST parity with the WS StartSession).
    // Seeded as an InitialIntentEvent before the run so it lands in the decision journal.
    val intent: String? = null,
)

@Serializable
data class SessionSummaryResponse(
    val sessionId: String,
    val status: String,
    val name: String? = null,
    val intent: String? = null,
    val workflowId: String? = null,
    val stageCount: Int = 0,
    val createdAt: String? = null,
    val lastActivityAt: String? = null,
)

@Serializable
data class SessionStateResponse(val sessionId: String, val status: String, val createdAt: String?)

@Serializable
data class StartSessionResponse(val sessionId: String)

@Serializable
data class ApproveStageRequest(val decision: String, val steeringNote: String? = null)

@Serializable
data class ApproveSourcesRequest(val urls: List<String>)

@Serializable
data class ApproveSourcesResponse(val grantedHosts: List<String>)

private val log = LoggerFactory.getLogger("com.correx.apps.server.routes.SessionRoutes")

fun Route.sessionRoutes(module: ServerModule) {
    val streamHandler = SessionStreamHandler(module)
    route("/sessions") {
        get {
            val summaries = module.listSessionSummaries().map { s ->
                SessionSummaryResponse(
                    sessionId = s.sessionId.value,
                    status = s.status,
                    name = s.name,
                    intent = s.intent,
                    workflowId = s.workflowId,
                    stageCount = s.stageCount,
                    createdAt = s.createdAt?.toString(),
                    lastActivityAt = s.lastActivityAt?.toString(),
                )
            }
            call.respond(summaries)
        }
        startSessionRoute(module)
        route("/{id}") {
            getSessionRoute(module)
            cancelSessionRoute(module)
            approveStageRoute(module)
            clarifyStageRoute(module)
            approveSourcesRoute(module)
            undoSessionRoute(module)
            resumeSessionRoute(module)
            getEventsRoute(module)
            getReplayRoute(module)
            getMetricsRoute(module)
            webSocket("/stream") {
                val id = call.parameters["id"] ?: return@webSocket
                val lastEventId = call.request.queryParameters["lastEventId"]?.toLongOrNull()
                streamHandler.handle(this, TypeId(id), lastEventId)
            }
        }
    }
}

private fun Route.startSessionRoute(module: ServerModule) {
    post {
        val body = call.receive<StartSessionRequest>()
        val graph = module.workflowRegistry.find(body.workflowId)
            ?: return@post call.respond(HttpStatusCode.BadRequest, "Unknown workflowId: ${body.workflowId}")
        val sessionId: SessionId = TypeId(UUID.randomUUID().toString())
        // Anchor the session's workspace (invariant #9) so path containment, project profile and
        // grants resolve against a bound root. REST carries no cwd, so the resolver's default/
        // fallback root is bound — parity with the WS StartSession path.
        val workspace = module.bindWorkspace(sessionId, null)
        body.intent?.takeIf { it.isNotBlank() }?.let { intent ->
            EventDispatcher(module.eventStore).emit(InitialIntentEvent(sessionId, intent), sessionId)
        }
        module.launchSessionRun(sessionId, graph, module.orchestrationConfig().copy(workspace = workspace))
        call.respond(HttpStatusCode.Accepted, StartSessionResponse(sessionId.value))
    }
}

private fun Route.getSessionRoute(module: ServerModule) {
    get {
        val id = call.parameters["id"]
            ?: return@get call.respond(HttpStatusCode.BadRequest, "Missing session id")
        val session = runCatching { module.sessionRepository.getSession(TypeId(id)) }.getOrNull()
            ?: return@get call.respond(HttpStatusCode.NotFound, "Session not found")
        call.respond(
            SessionStateResponse(
                sessionId = session.sessionId.value,
                status = session.state.status.name,
                createdAt = session.state.createdAt?.toString(),
            )
        )
    }
}

private fun Route.cancelSessionRoute(module: ServerModule) {
    post("/cancel") {
        val id = call.parameters["id"]
            ?: return@post call.respond(HttpStatusCode.BadRequest, "Missing session id")
        module.orchestrator.cancel(TypeId(id))
        call.respond(HttpStatusCode.OK)
    }
}

// Batch fetch-approval at the source-list level (BACKLOG §D): the operator approves a research
// source list once and egress is granted for all its hosts in one EgressHostsGrantedEvent, so the
// session's subsequent web_fetches to those hosts auto-clear instead of prompting per URL. Body
// carries the source list's URLs (or its source_dossier source lines).
private fun Route.approveSourcesRoute(module: ServerModule) {
    post("/approve-sources") {
        val id = call.parameters["id"]
            ?: return@post call.respond(HttpStatusCode.BadRequest, "Missing session id")
        val body = call.receive<ApproveSourcesRequest>()
        val granted = approveSourceList(
            dispatcher = EventDispatcher(module.eventStore),
            sessionId = TypeId(id),
            urls = body.urls,
        )
        call.respond(HttpStatusCode.OK, ApproveSourcesResponse(grantedHosts = granted.sorted()))
    }
}

private fun Route.undoSessionRoute(module: ServerModule) {
    post("/undo") {
        val id = call.parameters["id"]
            ?: return@post call.respond(HttpStatusCode.BadRequest, "Missing session id")
        val summary = module.sessionUndoService.undo(TypeId(id))
        call.respond(HttpStatusCode.OK, summary)
    }
}

private fun Route.resumeSessionRoute(module: ServerModule) {
    post("/resume") {
        val id = call.parameters["id"]
            ?: return@post call.respond(HttpStatusCode.BadRequest, "Missing session id")
        val orchState = runCatching { module.orchestrationRepository.getState(TypeId(id)) }.getOrNull()
            ?: return@post call.respond(HttpStatusCode.NotFound, "Session not found")
        val workflowId = orchState.workflowId.takeIf { it.isNotBlank() }
            ?: return@post call.respond(HttpStatusCode.BadRequest, "Session has no workflowId")
        val graph = module.workflowRegistry.find(workflowId)
            ?: module.freestyleResumeGraph(TypeId(id), workflowId)
            ?: return@post call.respond(HttpStatusCode.BadRequest, "Unknown workflowId: $workflowId")
        module.launchSessionResumeWithRehydrate(TypeId(id), graph)
        call.respond(HttpStatusCode.Accepted)
    }
}

private fun Route.getEventsRoute(module: ServerModule) {
    get("/events") {
        val id = call.parameters["id"]
            ?: return@get call.respond(HttpStatusCode.BadRequest, "Missing session id")
        val fromSeqParam = call.request.queryParameters["fromSeq"]?.toLongOrNull()
        val fromSeq = fromSeqParam ?: 0L
        val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: DEFAULT_EVENT_LIMIT
        val typeFilter = call.request.queryParameters["type"]
        val mapped = module.eventStore.readFrom(TypeId(id), fromSeq)
            .asSequence()
            .map { stored ->
                val payloadJson = eventJson.encodeToJsonElement(EventPayload.serializer(), stored.payload)
                val type = payloadDiscriminator(payloadJson)
                EventRow(
                    sequence = stored.sequence,
                    eventId = stored.metadata.eventId.value,
                    type = type,
                    timestamp = stored.metadata.timestamp.toString(),
                    causationId = stored.metadata.causationId?.value,
                    correlationId = stored.metadata.correlationId?.value,
                    payload = payloadJson,
                )
            }
            .filter { typeFilter == null || it.type == typeFilter }
            .toList()
        // No explicit fromSeq = a "what's happening now" tail: return the most recent `limit` events
        // (a pending ApprovalRequested lives at the tail, so the oldest-200 default hid it from a
        // headless/REST operator — the very caller POST /approve exists to serve). Explicit fromSeq
        // keeps forward, ascending pagination from that point.
        val rows = if (fromSeqParam == null) mapped.takeLast(limit) else mapped.take(limit)
        call.respond(rows)
    }
}

private fun Route.getReplayRoute(module: ServerModule) {
    val service = ReplayInspectionService(module.eventStore)
    get("/replay") {
        val id = call.parameters["id"]
            ?: return@get call.respond(HttpStatusCode.BadRequest, "Missing session id")
        val report = service.inspect(TypeId(id))
        call.respond(report)
    }
}

private fun Route.getMetricsRoute(module: ServerModule) {
    val service = MetricsInspectionService(module.eventStore)
    get("/metrics") {
        val id = call.parameters["id"]
            ?: return@get call.respond(HttpStatusCode.BadRequest, "Missing session id")
        val report = service.inspect(TypeId(id))
        call.respond(report)
    }
}
