package com.correx.core.events

import com.correx.core.approvals.ApprovalOutcome
import com.correx.core.approvals.ApprovalStatus
import com.correx.core.approvals.Tier
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
import com.correx.core.events.events.ApprovalRequestedEvent
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.IdeaPromotedEvent
import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.RiskAssessedEvent
import com.correx.core.events.events.SteeringNoteAddedEvent
import com.correx.core.events.events.ToolExecutionFailedEvent
import com.correx.core.events.events.WorkflowCompletedEvent
import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.risk.RiskAction
import com.correx.core.events.risk.RiskLevel
import com.correx.core.events.risk.RiskSignal
import com.correx.core.events.risk.RiskSummary
import com.correx.core.events.serialization.eventJson
import com.correx.core.events.types.ApprovalDecisionId
import com.correx.core.events.types.ApprovalRequestId
import com.correx.core.events.types.ProjectId
import com.correx.core.events.types.RiskSummaryId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.ToolInvocationId
import com.correx.core.events.types.ValidationReportId
import kotlinx.datetime.Instant
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertDoesNotThrow
import org.junit.jupiter.api.Test

class EventSerializationHardeningTest {

    private val sessionId = SessionId("s-1")
    private val stageId = StageId("st-1")
    private val ts = Instant.parse("2026-01-01T00:00:00Z")

    private val payloads: List<Pair<String, EventPayload>> = listOf(
        "ToolExecutionFailed" to ToolExecutionFailedEvent(
            invocationId = ToolInvocationId("inv-1"),
            sessionId = sessionId,
            toolName = "echo",
            reason = "boom"
        ),
        "ApprovalDecisionResolved" to ApprovalDecisionResolvedEvent(
            decisionId = ApprovalDecisionId("d-1"),
            requestId = ApprovalRequestId("r-1"),
            outcome = ApprovalOutcome.APPROVED,
            status = ApprovalStatus.COMPLETED,
            tier = Tier.T2,
            resolutionTimestamp = ts,
            reason = null
        ),
        "SteeringNoteAdded" to SteeringNoteAddedEvent(
            sessionId = sessionId,
            content = "test steering note",
            stageId = stageId,
        ),
        "OrchestrationPaused" to OrchestrationPausedEvent(
            sessionId = sessionId,
            stageId = stageId,
            reason = "APPROVAL_PENDING"
        ),
        "RiskAssessed" to RiskAssessedEvent(
            sessionId = sessionId,
            stageId = stageId,
            riskSummaryId = RiskSummaryId("rs-1"),
            level = RiskLevel.MEDIUM,
            action = RiskAction.PROCEED
        ),
        "WorkflowStarted" to WorkflowStartedEvent(
            sessionId = sessionId,
            workflowId = "test-wf",
            startStageId = stageId,
        ),
        "WorkflowCompleted" to WorkflowCompletedEvent(
            sessionId = sessionId,
            terminalStageId = stageId,
            totalStages = 1,
        ),
        "IdeaPromoted" to IdeaPromotedEvent(
            ideaId = "idea-1",
            sessionId = sessionId,
            text = "cache the repo map across sessions",
            timestampMs = 1_700_000_000_000,
        ),
    )

    @Test
    fun `discriminator field equals pinned SerialName for all representative events`() {
        for ((expectedType, payload) in payloads) {
            val json = eventJson.encodeToString(EventPayload.serializer(), payload)
            val element = Json.parseToJsonElement(json)
            val actualType = element.jsonObject["type"]?.toString()?.removeSurrounding("\"")
            assertEquals(
                expectedType,
                actualType,
                "Expected discriminator 'type'=\"$expectedType\" for ${payload::class.simpleName}"
            )
        }
    }

    @Test
    fun `all representative events round-trip through EventPayload polymorphic serializer`() {
        for ((_, payload) in payloads) {
            val json = eventJson.encodeToString(EventPayload.serializer(), payload)
            val decoded = eventJson.decodeFromString(EventPayload.serializer(), json)
            assertEquals(payload, decoded, "Round-trip failed for ${payload::class.simpleName}")
        }
    }

    @Test
    fun `unknown fields in JSON are tolerated (ignoreUnknownKeys=true)`() {
        val event = WorkflowStartedEvent(sessionId = sessionId, workflowId = "twf", startStageId = stageId)
        val json = eventJson.encodeToString(EventPayload.serializer(), event)
        val withExtra = json.replace("{", "{\"bogusField\":123,")
        assertDoesNotThrow {
            eventJson.decodeFromString(EventPayload.serializer(), withExtra)
        }
    }

    @Test
    fun `ApprovalRequestedEvent with non-null riskSummary round-trips through EventPayload polymorphic serializer`() {
        val riskSummary = RiskSummary(
            level = RiskLevel.HIGH,
            signals = listOf(
                RiskSignal.ValidationErrors(errorCount = 3),
                RiskSignal.RepeatedFailure(reason = "parse error", count = 2),
            ),
            recommendedAction = RiskAction.PROMPT_USER,
            rationale = listOf("[ERR001] Schema mismatch", "[ERR002] Missing required field"),
        )
        val event: EventPayload = ApprovalRequestedEvent(
            requestId = ApprovalRequestId("req-rt-1"),
            tier = Tier.T3,
            validationReportId = ValidationReportId("vr-rt-1"),
            riskSummaryId = null,
            riskSummary = riskSummary,
            sessionId = sessionId,
            stageId = stageId,
            projectId = ProjectId("proj-1"),
        )
        val json = eventJson.encodeToString(EventPayload.serializer(), event)
        val decoded = eventJson.decodeFromString(EventPayload.serializer(), json)
        assertEquals(event, decoded)
    }
}
