"""
Study Interview Engine

Main orchestrator for the interview process.
Coordinates question flow, state management, validation, and blueprint generation.
"""

from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Literal
import uuid

from .interview_state import (
    InterviewState,
    InterviewPhase,
    InterviewStateManager,
    AnsweredQuestion,
    LogEntry,
)
from .question_engine import QuestionEngine, Question
from .interview_presenter import InterviewPresenter, ClaudePresenter
from .engineering_validator import EngineeringValidator, ValidationResult, AntiPattern
from .interview_intelligence import InterviewIntelligence
from .study_blueprint import StudyBlueprint, BlueprintBuilder


@dataclass
class InterviewSession:
    """Represents an active interview session."""
    session_id: str
    study_name: str
    study_path: Path
    started_at: datetime
    current_phase: InterviewPhase
    introspection: Dict[str, Any]
    is_complete: bool = False
    is_resumed: bool = False


@dataclass
class NextAction:
    """What should happen after processing an answer."""
    action_type: Literal["ask_question", "show_summary", "validate", "generate", "error", "confirm_warning"]
    question: Optional[Question] = None
    message: Optional[str] = None
    warnings: List[str] = field(default_factory=list)
    blueprint: Optional[StudyBlueprint] = None
    anti_patterns: List[AntiPattern] = field(default_factory=list)


class StudyInterviewEngine:
    """
    Main orchestrator for study interviews.

    Manages the complete interview lifecycle:
    1. Start or resume interview
    2. Present questions via presenter
    3. Process answers with validation
    4. Generate blueprint for review
    5. Handle modifications
    6. Coordinate study generation
    """

    def __init__(
        self,
        study_path: Path,
        presenter: Optional[InterviewPresenter] = None
    ):
        """
        Initialize interview engine.

        Args:
            study_path: Path to the study directory
            presenter: Presentation layer (defaults to ClaudePresenter)
        """
        self.study_path = Path(study_path)
        self.presenter = presenter or ClaudePresenter()
        self.state_manager = InterviewStateManager(self.study_path)
        self.question_engine = QuestionEngine()
        self.validator = EngineeringValidator()
        self.intelligence = InterviewIntelligence()
        self.blueprint_builder = BlueprintBuilder()

        # Current state
        self.state: Optional[InterviewState] = None
        self.introspection: Dict[str, Any] = {}
        self.current_question: Optional[Question] = None
        self.session: Optional[InterviewSession] = None

        # Estimated questions (for progress)
        self.estimated_total_questions = 12  # Will be updated based on complexity

    def start_interview(
        self,
        study_name: str,
        model_path: Optional[Path] = None,
        introspection: Optional[Dict[str, Any]] = None
    ) -> InterviewSession:
        """
        Start a new interview or resume existing one.

        Args:
            study_name: Name for the study
            model_path: Path to the NX model (optional)
            introspection: Pre-computed introspection results (optional)

        Returns:
            InterviewSession representing the active interview
        """
        # Check for existing state
        existing_state = self.state_manager.load_state()

        if existing_state and not existing_state.is_complete():
            # Resume existing interview
            self.state = existing_state
            self.introspection = existing_state.introspection

            self.session = InterviewSession(
                session_id=existing_state.session_id,
                study_name=existing_state.study_name,
                study_path=self.study_path,
                started_at=datetime.fromisoformat(existing_state.started_at),
                current_phase=existing_state.get_phase(),
                introspection=self.introspection,
                is_resumed=True,
            )

            return self.session

        # Start new interview
        self.state = InterviewState(
            session_id=str(uuid.uuid4()),
            study_name=study_name,
            study_path=str(self.study_path),
            current_phase=InterviewPhase.INTROSPECTION.value,
        )

        # Store introspection if provided
        if introspection:
            self.introspection = introspection
            self.state.introspection = introspection
            # Move to problem definition if introspection already done
            self.state.set_phase(InterviewPhase.PROBLEM_DEFINITION)

        # Save initial state
        self.state_manager.save_state(self.state)

        self.session = InterviewSession(
            session_id=self.state.session_id,
            study_name=study_name,
            study_path=self.study_path,
            started_at=datetime.now(),
            current_phase=self.state.get_phase(),
            introspection=self.introspection,
        )

        return self.session

    def get_first_question(self) -> NextAction:
        """
        Get the first question to ask.

        Returns:
            NextAction with the first question
        """
        if self.state is None:
            return NextAction(
                action_type="error",
                message="Interview not started. Call start_interview() first."
            )

        # Get next question
        next_q = self.question_engine.get_next_question(self.state, self.introspection)

        if next_q is None:
            # No questions - should not happen at start
            return NextAction(
                action_type="error",
                message="No questions available."
            )

        self.current_question = next_q

        return NextAction(
            action_type="ask_question",
            question=next_q,
            message=self.presenter.present_question(
                next_q,
                question_number=self.state.current_question_count() + 1,
                total_questions=self.estimated_total_questions,
                category_name=self._get_category_name(next_q.category),
            )
        )

    def process_answer(self, answer: str) -> NextAction:
        """
        Process user answer and determine next action.

        Args:
            answer: User's answer (natural language)

        Returns:
            NextAction indicating what to do next
        """
        if self.state is None or self.current_question is None:
            return NextAction(
                action_type="error",
                message="No active question. Call get_first_question() or get_next_question()."
            )

        question = self.current_question

        # 1. Parse answer based on question type
        parsed = self.presenter.parse_response(answer, question)

        # 2. Validate answer
        is_valid, error_msg = self.question_engine.validate_answer(parsed, question)
        if not is_valid:
            return NextAction(
                action_type="error",
                message=f"Invalid answer: {error_msg}",
                question=question,  # Re-ask same question
            )

        # 3. Store answer
        self._store_answer(question, answer, parsed)

        # 4. Update phase if needed
        self._update_phase(question)

        # 5. Update complexity after initial questions
        if question.category == "problem_definition":
            self._update_complexity()

        # 6. Check for warnings/anti-patterns
        anti_patterns = self.validator.detect_anti_patterns(self.state, self.introspection)
        new_warnings = [ap.description for ap in anti_patterns if ap.severity in ["error", "warning"]]

        # Filter to only new warnings
        existing_warnings = set(self.state.warnings)
        for w in new_warnings:
            if w not in existing_warnings:
                self.state.add_warning(w)

        # 7. Check if we should show anti-pattern warnings
        blocking_patterns = [ap for ap in anti_patterns if ap.severity == "error" and not ap.acknowledged]
        if blocking_patterns:
            return NextAction(
                action_type="confirm_warning",
                message=self._format_anti_pattern_warnings(blocking_patterns),
                anti_patterns=blocking_patterns,
            )

        # 8. Get next question
        next_q = self.question_engine.get_next_question(self.state, self.introspection)

        if next_q is None:
            # Interview complete - generate blueprint
            return self._finalize_interview()

        self.current_question = next_q

        return NextAction(
            action_type="ask_question",
            question=next_q,
            message=self.presenter.present_question(
                next_q,
                question_number=self.state.current_question_count() + 1,
                total_questions=self.estimated_total_questions,
                category_name=self._get_category_name(next_q.category),
            ),
            warnings=[w for w in self.state.warnings if w not in self.state.warnings_acknowledged],
        )

    def acknowledge_warnings(self, acknowledged: bool = True) -> NextAction:
        """
        Acknowledge current warnings and continue.

        Args:
            acknowledged: Whether user acknowledged warnings

        Returns:
            NextAction (continue or abort)
        """
        if not acknowledged:
            return NextAction(
                action_type="error",
                message="Interview paused. Please fix the issues and restart, or acknowledge warnings to proceed."
            )

        # Mark all current warnings as acknowledged
        for w in self.state.warnings:
            self.state.acknowledge_warning(w)

        # Continue to next question
        next_q = self.question_engine.get_next_question(self.state, self.introspection)

        if next_q is None:
            return self._finalize_interview()

        self.current_question = next_q

        return NextAction(
            action_type="ask_question",
            question=next_q,
            message=self.presenter.present_question(
                next_q,
                question_number=self.state.current_question_count() + 1,
                total_questions=self.estimated_total_questions,
                category_name=self._get_category_name(next_q.category),
            )
        )

    def generate_blueprint(self) -> StudyBlueprint:
        """
        Generate study blueprint from interview state.

        Returns:
            StudyBlueprint ready for generation
        """
        if self.state is None:
            raise ValueError("No interview state available")

        blueprint = self.blueprint_builder.from_interview_state(
            self.state,
            self.introspection
        )

        # Store in state
        self.state.blueprint = blueprint.to_dict()
        self.state_manager.save_state(self.state)

        return blueprint

    def modify_blueprint(self, changes: Dict[str, Any]) -> StudyBlueprint:
        """
        Apply what-if modifications to the blueprint.

        Args:
            changes: Dictionary of changes to apply

        Returns:
            Modified StudyBlueprint
        """
        if self.state is None or self.state.blueprint is None:
            raise ValueError("No blueprint available to modify")

        blueprint = StudyBlueprint.from_dict(self.state.blueprint)

        # Apply changes
        for key, value in changes.items():
            if key == "n_trials":
                blueprint.n_trials = int(value)
            elif key == "sampler":
                blueprint.sampler = value
            elif key == "add_constraint":
                # Handle adding constraints
                pass
            elif key == "remove_constraint":
                # Handle removing constraints
                pass
            # Add more modification types as needed

        # Re-validate
        validation_errors = blueprint.validate()
        if validation_errors:
            raise ValueError(f"Invalid modifications: {validation_errors}")

        # Update state
        self.state.blueprint = blueprint.to_dict()
        self.state_manager.save_state(self.state)

        return blueprint

    def confirm_blueprint(self) -> bool:
        """
        Confirm blueprint and mark interview as complete.

        Returns:
            True if successful
        """
        if self.state is None:
            return False

        self.state.set_phase(InterviewPhase.COMPLETE)
        self.state_manager.save_state(self.state)

        # Finalize log
        self.state_manager.finalize_log(self.state)

        return True

    def get_current_state(self) -> Optional[InterviewState]:
        """Get current interview state."""
        return self.state

    def get_progress(self) -> str:
        """Get formatted progress string."""
        if self.state is None:
            return "No active interview"

        return self.presenter.show_progress(
            self.state.current_question_count(),
            self.estimated_total_questions,
            self._get_phase_name(self.state.current_phase)
        )

    def reset_interview(self) -> None:
        """Reset interview and start fresh."""
        self.state_manager.delete_state()
        self.state = None
        self.current_question = None
        self.session = None

    # Private methods

    def _store_answer(self, question: Question, raw: str, parsed: Any) -> None:
        """Store answer in state."""
        # Create answered question record
        answered = AnsweredQuestion(
            question_id=question.id,
            answered_at=datetime.now().isoformat(),
            raw_response=raw,
            parsed_value=parsed,
        )

        self.state.add_answered_question(answered)

        # Map to answer field
        self._map_answer_to_field(question.maps_to, parsed)

        # Create log entry
        log_entry = LogEntry(
            timestamp=datetime.now(),
            question_id=question.id,
            question_text=question.text,
            answer_raw=raw,
            answer_parsed=parsed,
        )

        self.state_manager.append_log(log_entry)
        self.state_manager.save_state(self.state)

    def _map_answer_to_field(self, maps_to: str, value: Any) -> None:
        """Map parsed value to the appropriate answer field."""
        if not maps_to:
            return

        # Handle array indexing: "objectives[0].goal"
        if "[" in maps_to:
            import re
            match = re.match(r"(\w+)\[(\d+)\]\.(\w+)", maps_to)
            if match:
                array_name, idx, field = match.groups()
                idx = int(idx)

                # Ensure array exists
                if array_name not in self.state.answers:
                    self.state.answers[array_name] = []

                # Ensure element exists
                while len(self.state.answers[array_name]) <= idx:
                    self.state.answers[array_name].append({})

                self.state.answers[array_name][idx][field] = value
                return

        # Handle nested fields: "constraints.max_stress"
        if "." in maps_to:
            parts = maps_to.split(".")
            current = self.state.answers

            for part in parts[:-1]:
                if part not in current:
                    current[part] = {}
                current = current[part]

            current[parts[-1]] = value
            return

        # Simple field
        self.state.set_answer(maps_to, value)

    def _update_phase(self, question: Question) -> None:
        """Update interview phase based on question category."""
        category_to_phase = {
            "problem_definition": InterviewPhase.PROBLEM_DEFINITION,
            "objectives": InterviewPhase.OBJECTIVES,
            "constraints": InterviewPhase.CONSTRAINTS,
            "design_variables": InterviewPhase.DESIGN_VARIABLES,
            "physics_config": InterviewPhase.DESIGN_VARIABLES,
            "optimization_settings": InterviewPhase.VALIDATION,
            "validation": InterviewPhase.VALIDATION,
        }

        new_phase = category_to_phase.get(question.category)
        if new_phase and new_phase != self.state.get_phase():
            self.state.set_phase(new_phase)

    def _update_complexity(self) -> None:
        """Update complexity estimate after initial questions."""
        complexity = self.intelligence.determine_complexity(self.state, self.introspection)
        self.state.complexity = complexity

        # Adjust estimated questions
        if complexity == "simple":
            self.estimated_total_questions = 8
        elif complexity == "moderate":
            self.estimated_total_questions = 12
        else:
            self.estimated_total_questions = 16

    def _finalize_interview(self) -> NextAction:
        """Finalize interview and show summary."""
        self.state.set_phase(InterviewPhase.REVIEW)

        blueprint = self.generate_blueprint()

        return NextAction(
            action_type="show_summary",
            message=self.presenter.show_summary(blueprint),
            blueprint=blueprint,
        )

    def _format_anti_pattern_warnings(self, patterns: List[AntiPattern]) -> str:
        """Format anti-pattern warnings for display."""
        lines = ["**Issues Detected:**", ""]

        for ap in patterns:
            severity_icon = "X" if ap.severity == "error" else "!"
            lines.append(f"[{severity_icon}] **{ap.name}**")
            lines.append(f"   {ap.description}")
            if ap.fix_suggestion:
                lines.append(f"   *Suggestion*: {ap.fix_suggestion}")
            lines.append("")

        lines.append("Would you like to proceed anyway? Type **yes** to continue or **no** to go back and fix.")

        return "\n".join(lines)

    def _get_category_name(self, category: str) -> str:
        """Get human-readable category name."""
        names = {
            "problem_definition": "Problem Definition",
            "objectives": "Optimization Goals",
            "constraints": "Constraints",
            "design_variables": "Design Variables",
            "physics_config": "Physics Configuration",
            "optimization_settings": "Optimization Settings",
            "validation": "Validation",
        }
        return names.get(category, category.replace("_", " ").title())

    def _get_phase_name(self, phase: str) -> str:
        """Get human-readable phase name."""
        names = {
            "introspection": "Model Analysis",
            "problem_definition": "Problem Definition",
            "objectives": "Setting Objectives",
            "constraints": "Defining Constraints",
            "design_variables": "Selecting Variables",
            "validation": "Validation",
            "review": "Review & Confirm",
            "complete": "Complete",
        }
        return names.get(phase, phase.replace("_", " ").title())


# Convenience function for quick interview
def run_interview(
    study_path: Path,
    study_name: str,
    introspection: Optional[Dict[str, Any]] = None
) -> StudyInterviewEngine:
    """
    Create and start an interview engine.

    Args:
        study_path: Path to study directory
        study_name: Study name
        introspection: Optional introspection results

    Returns:
        Configured StudyInterviewEngine ready for use
    """
    engine = StudyInterviewEngine(study_path)
    engine.start_interview(study_name, introspection=introspection)
    return engine
