"""
Script Flow Tester for AI Service.
[AC-AISVC-101, AC-AISVC-102] Flow simulation and coverage analysis.
"""

import logging
import re
import time
from dataclasses import dataclass, field
from typing import Any

from app.models.entities import ScriptFlow

logger = logging.getLogger(__name__)


@dataclass
class MatchedCondition:
    """Matched condition details."""

    type: str
    goto_step: int
    keywords: list[str] | None = None
    pattern: str | None = None


@dataclass
class FlowSimulationStep:
    """Single step in flow simulation."""

    stepNo: int
    botMessage: str
    userInput: str
    matchedCondition: MatchedCondition | None
    nextStep: int | None
    durationMs: int = 0


@dataclass
class FlowSimulationResult:
    """Result of flow simulation."""

    flowId: str
    flowName: str
    simulation: list[FlowSimulationStep] = field(default_factory=list)
    result: dict[str, Any] = field(default_factory=dict)
    coverage: dict[str, Any] = field(default_factory=dict)
    issues: list[str] = field(default_factory=list)


class ScriptFlowTester:
    """
    [AC-AISVC-101, AC-AISVC-102] Script flow simulation and coverage analysis.

    Features:
    - Simulate flow execution with user inputs
    - Calculate step coverage rate
    - Detect issues: dead loops, low coverage, uncovered branches
    - No database modification (read-only simulation)
    """

    MIN_COVERAGE_THRESHOLD = 0.8
    MAX_SIMULATION_STEPS_MULTIPLIER = 2

    def simulate_flow(
        self,
        flow: ScriptFlow,
        user_inputs: list[str],
    ) -> FlowSimulationResult:
        """
        [AC-AISVC-101] Simulate flow execution and analyze coverage.

        Args:
            flow: ScriptFlow entity to simulate
            user_inputs: List of user inputs to feed into the flow

        Returns:
            FlowSimulationResult with simulation steps, coverage, and issues
        """
        logger.info(
            f"[AC-AISVC-101] Starting flow simulation: flow_id={flow.id}, "
            f"flow_name={flow.name}, inputs_count={len(user_inputs)}"
        )

        simulation: list[FlowSimulationStep] = []
        current_step = 1
        visited_steps: set[int] = set()
        total_steps = len(flow.steps)
        total_duration_ms = 0
        final_message: str | None = None
        completed = False

        for user_input in user_inputs:
            if current_step > total_steps:
                completed = True
                break

            step_start = time.time()
            step_def = flow.steps[current_step - 1]
            visited_steps.add(current_step)

            matched_condition, next_step = self._match_next_step(step_def, user_input)

            if next_step is None:
                default_next = step_def.get("default_next")
                if default_next:
                    next_step = default_next
                    matched_condition = MatchedCondition(
                        type="default",
                        goto_step=default_next,
                    )
                else:
                    next_step = current_step

            simulation.append(
                FlowSimulationStep(
                    stepNo=current_step,
                    botMessage=step_def.get("content", ""),
                    userInput=user_input,
                    matchedCondition=matched_condition,
                    nextStep=next_step,
                    durationMs=int((time.time() - step_start) * 1000),
                )
            )

            total_duration_ms += simulation[-1].durationMs

            if next_step > total_steps:
                completed = True
                final_message = step_def.get("content", "")
                break

            current_step = next_step

            if len(simulation) > total_steps * self.MAX_SIMULATION_STEPS_MULTIPLIER:
                logger.warning(
                    f"[AC-AISVC-101] Simulation exceeded max steps: "
                    f"flow_id={flow.id}, steps={len(simulation)}"
                )
                break

        covered_steps = len(visited_steps)
        coverage_rate = covered_steps / total_steps if total_steps > 0 else 0.0
        uncovered_steps = set(range(1, total_steps + 1)) - visited_steps

        issues = self._detect_issues(
            simulation=simulation,
            total_steps=total_steps,
            coverage_rate=coverage_rate,
            uncovered_steps=uncovered_steps,
        )

        result = FlowSimulationResult(
            flowId=str(flow.id),
            flowName=flow.name,
            simulation=simulation,
            result={
                "completed": completed,
                "totalSteps": total_steps,
                "totalDurationMs": total_duration_ms,
                "finalMessage": final_message,
            },
            coverage={
                "totalSteps": total_steps,
                "coveredSteps": covered_steps,
                "coverageRate": round(coverage_rate, 2),
                "uncoveredSteps": sorted(list(uncovered_steps)),
            },
            issues=issues,
        )

        logger.info(
            f"[AC-AISVC-101] Flow simulation completed: flow_id={flow.id}, "
            f"coverage_rate={coverage_rate:.2%}, issues_count={len(issues)}"
        )

        return result

    def _match_next_step(
        self,
        step_def: dict[str, Any],
        user_input: str,
    ) -> tuple[MatchedCondition | None, int | None]:
        """
        [AC-AISVC-101] Match user input against next_conditions.

        Args:
            step_def: Current step definition
            user_input: User's input message

        Returns:
            Tuple of (matched_condition, goto_step) or (None, None)
        """
        next_conditions = step_def.get("next_conditions", [])
        if not next_conditions:
            return None, None

        user_input_lower = user_input.lower()

        for condition in next_conditions:
            keywords = condition.get("keywords", [])
            for keyword in keywords:
                if keyword.lower() in user_input_lower:
                    matched = MatchedCondition(
                        type="keyword",
                        keywords=keywords,
                        goto_step=condition.get("goto_step"),
                    )
                    return matched, condition.get("goto_step")

            pattern = condition.get("pattern")
            if pattern:
                try:
                    if re.search(pattern, user_input, re.IGNORECASE):
                        matched = MatchedCondition(
                            type="pattern",
                            pattern=pattern,
                            goto_step=condition.get("goto_step"),
                        )
                        return matched, condition.get("goto_step")
                except re.error:
                    logger.warning(f"Invalid regex pattern: {pattern}")

        return None, None

    def _detect_issues(
        self,
        simulation: list[FlowSimulationStep],
        total_steps: int,
        coverage_rate: float,
        uncovered_steps: set[int],
    ) -> list[str]:
        """
        [AC-AISVC-102] Detect issues in flow simulation.

        Args:
            simulation: List of simulation steps
            total_steps: Total number of steps in flow
            coverage_rate: Coverage rate (0.0 to 1.0)
            uncovered_steps: Set of uncovered step numbers

        Returns:
            List of issue descriptions
        """
        issues: list[str] = []

        if coverage_rate < self.MIN_COVERAGE_THRESHOLD:
            issues.append(
                f"流程覆盖率低于 {int(self.MIN_COVERAGE_THRESHOLD * 100)}%，建议增加测试用例"
            )

        if len(simulation) > total_steps * self.MAX_SIMULATION_STEPS_MULTIPLIER:
            issues.append("检测到可能的死循环")

        if uncovered_steps:
            issues.append(f"未覆盖步骤：{sorted(list(uncovered_steps))}")

        step_visit_count: dict[int, int] = {}
        for step in simulation:
            step_visit_count[step.stepNo] = step_visit_count.get(step.stepNo, 0) + 1

        for step_no, count in step_visit_count.items():
            if count > 3:
                issues.append(f"步骤 {step_no} 被重复访问 {count} 次，可能存在逻辑问题")

        return issues

    def to_dict(self, result: FlowSimulationResult) -> dict[str, Any]:
        """Convert simulation result to API response dict."""
        simulation_data = []
        for step in result.simulation:
            step_data = {
                "stepNo": step.stepNo,
                "botMessage": step.botMessage,
                "userInput": step.userInput,
                "nextStep": step.nextStep,
                "durationMs": step.durationMs,
            }
            if step.matchedCondition:
                step_data["matchedCondition"] = {
                    "type": step.matchedCondition.type,
                    "gotoStep": step.matchedCondition.goto_step,
                }
                if step.matchedCondition.keywords:
                    step_data["matchedCondition"]["keywords"] = step.matchedCondition.keywords
                if step.matchedCondition.pattern:
                    step_data["matchedCondition"]["pattern"] = step.matchedCondition.pattern
            else:
                step_data["matchedCondition"] = None
            simulation_data.append(step_data)

        return {
            "flowId": result.flowId,
            "flowName": result.flowName,
            "simulation": simulation_data,
            "result": result.result,
            "coverage": result.coverage,
            "issues": result.issues,
        }
