package com.correx.core.risk

import com.correx.core.events.execution.RetryPolicy
import com.correx.core.events.orchestration.OrchestrationState
import com.correx.core.events.orchestration.OrchestrationStatus
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.types.InferenceRequestId
import com.correx.core.events.types.ProviderId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.inference.InferenceRecord
import com.correx.core.inference.InferenceState
import com.correx.core.inference.InferenceStatus
import com.correx.core.validation.model.ValidationIssue
import com.correx.core.validation.model.ValidationReport
import com.correx.core.validation.model.ValidationSection
import com.correx.core.validation.model.ValidationSeverity
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class DefaultRiskAssessorTest {

    private val assessor = DefaultRiskAssessor()
    private val sessionId = SessionId("s1")
    private val stageId = StageId("stage-1")

    private fun emptyReport() = ValidationReport(sections = emptyList())
    private fun emptyInferenceState() = InferenceState()
    private fun baseState() = OrchestrationState(currentStageId = stageId, status = OrchestrationStatus.RUNNING)

    private fun ctx(
        report: ValidationReport = emptyReport(),
        state: OrchestrationState = baseState(),
        inferenceState: InferenceState = emptyInferenceState(),
    ) = RiskContext(
        validationReport = report,
        orchestrationState = state,
        inferenceState = inferenceState,
    )

    @Test
    fun `no signals yields LOW risk and PROCEED`() {
        val summary = assessor.assess(ctx())

        assertEquals(RiskLevel.LOW, summary.level)
        assertEquals(RiskAction.PROCEED, summary.recommendedAction)
        assertTrue(summary.signals.isEmpty())
    }

    @Test
    fun `validation errors produce ValidationErrors signal at HIGH`() {
        val report = ValidationReport(
            sections = listOf(
                ValidationSection(
                    name = "schema",
                    issues = listOf(
                        ValidationIssue(code = "MISSING_FIELD", message = "field required",
                            severity = ValidationSeverity.ERROR),
                        ValidationIssue(code = "MISSING_FIELD", message = "other field",
                            severity = ValidationSeverity.ERROR),
                    ),
                ),
            ),
        )

        val summary = assessor.assess(ctx(report = report))

        assertEquals(RiskLevel.HIGH, summary.level)
        assertEquals(RiskAction.PROMPT_USER, summary.recommendedAction)
        val signal = summary.signals.filterIsInstance<RiskSignal.ValidationErrors>().single()
        assertEquals(2, signal.errorCount)
    }

    @Test
    fun `validation warnings produce MEDIUM risk when no errors present`() {
        val report = ValidationReport(
            sections = listOf(
                ValidationSection(
                    name = "schema",
                    issues = listOf(
                        ValidationIssue(code = "MISSING_CONTEXT", message = "context incomplete",
                            severity = ValidationSeverity.WARNING),
                    ),
                ),
            ),
        )

        val summary = assessor.assess(ctx(report = report))

        assertEquals(RiskLevel.MEDIUM, summary.level)
        assertEquals(RiskAction.PROMPT_USER, summary.recommendedAction)
    }

    @Test
    fun `cycle issue in validation report produces CycleWithoutExit signal at MEDIUM`() {
        val report = ValidationReport(
            sections = listOf(
                ValidationSection(
                    name = "semantic",
                    issues = listOf(
                        ValidationIssue(code = "CYCLE_WITHOUT_POLICY", message = "cycle detected",
                            severity = ValidationSeverity.WARNING),
                    ),
                ),
            ),
        )

        val summary = assessor.assess(ctx(report = report))

        assertEquals(RiskLevel.MEDIUM, summary.level)
        val signal = summary.signals.filterIsInstance<RiskSignal.CycleWithoutExit>().single()
        assertEquals("CYCLE_WITHOUT_POLICY", signal.cycleId)
    }

    @Test
    fun `retryCount at maxAttempts minus one produces RepeatedFailure signal at HIGH`() {
        val state = OrchestrationState(
            currentStageId = stageId,
            status = OrchestrationStatus.RUNNING,
            retryCount = 2,
            failureReason = "inference failed",
            retryPolicy = RetryPolicy(maxAttempts = 3),
        )

        val summary = assessor.assess(ctx(state = state))

        assertEquals(RiskLevel.HIGH, summary.level)
        assertEquals(RiskAction.PROMPT_USER, summary.recommendedAction)
        val signal = summary.signals.filterIsInstance<RiskSignal.RepeatedFailure>().single()
        assertEquals(2, signal.count)
        assertEquals("inference failed", signal.reason)
    }

    @Test
    fun `no retry policy skips RepeatedFailure signal even at high retryCount`() {
        val state = OrchestrationState(
            currentStageId = stageId,
            status = OrchestrationStatus.RUNNING,
            retryCount = 99,
            retryPolicy = null,
        )

        val summary = assessor.assess(ctx(state = state))

        assertEquals(RiskLevel.LOW, summary.level)
        assertTrue(summary.signals.filterIsInstance<RiskSignal.RepeatedFailure>().isEmpty())
    }

    @Test
    fun `timed-out inference record for current stage produces InferenceTimeout signal at MEDIUM`() {
        val inferenceState = InferenceState(
            records = listOf(
                InferenceRecord(
                    requestId = InferenceRequestId("r1"),
                    sessionId = sessionId,
                    stageId = stageId,
                    providerId = ProviderId("p1"),
                    status = InferenceStatus.TIMED_OUT,
                    latencyMs = 30_000L,
                ),
            ),
        )

        val summary = assessor.assess(ctx(inferenceState = inferenceState))

        assertEquals(RiskLevel.MEDIUM, summary.level)
        val signal = summary.signals.filterIsInstance<RiskSignal.InferenceTimeout>().single()
        assertEquals(30_000L, signal.elapsedMs)
    }

    @Test
    fun `timed-out inference for different stage does not produce signal`() {
        val inferenceState = InferenceState(
            records = listOf(
                InferenceRecord(
                    requestId = InferenceRequestId("r1"),
                    sessionId = sessionId,
                    stageId = StageId("other-stage"),
                    providerId = ProviderId("p1"),
                    status = InferenceStatus.TIMED_OUT,
                    latencyMs = 30_000L,
                ),
            ),
        )

        val summary = assessor.assess(ctx(inferenceState = inferenceState))

        assertEquals(RiskLevel.LOW, summary.level)
        assertTrue(summary.signals.filterIsInstance<RiskSignal.InferenceTimeout>().isEmpty())
    }

    @Test
    fun `rationale contains formatted issue codes and messages`() {
        val report = ValidationReport(
            sections = listOf(
                ValidationSection(
                    name = "schema",
                    issues = listOf(
                        ValidationIssue(code = "MISSING_FIELD", message = "field required",
                            severity = ValidationSeverity.ERROR),
                        ValidationIssue(code = "INVALID_TYPE", message = "type mismatch",
                            severity = ValidationSeverity.WARNING),
                    ),
                ),
            ),
        )

        val summary = assessor.assess(ctx(report = report))

        assertTrue(summary.rationale.contains("[MISSING_FIELD] field required"))
        assertTrue(summary.rationale.contains("[INVALID_TYPE] type mismatch"))
        assertEquals(2, summary.rationale.size)
    }

    @Test
    fun `empty validation report yields empty rationale`() {
        val report = ValidationReport(sections = emptyList())

        val summary = assessor.assess(ctx(report = report))

        assertTrue(summary.rationale.isEmpty())
    }

    @Test
    fun `multiple signals fold to highest risk level`() {
        val report = ValidationReport(
            sections = listOf(
                ValidationSection(
                    name = "schema",
                    issues = listOf(
                        ValidationIssue(code = "MISSING_FIELD", message = "required",
                            severity = ValidationSeverity.ERROR),
                    ),
                ),
            ),
        )
        val state = OrchestrationState(
            currentStageId = stageId,
            status = OrchestrationStatus.RUNNING,
            retryCount = 2,
            failureReason = "timeout",
            retryPolicy = RetryPolicy(maxAttempts = 3),
        )

        val summary = assessor.assess(ctx(report = report, state = state))

        assertEquals(RiskLevel.HIGH, summary.level)
        assertEquals(2, summary.signals.size)
    }
}
