"""
DSL generator from LLM spec analysis.

Generates DAZZLE DSL code from SpecAnalysis results and user answers.
"""

import logging
from pathlib import Path

from .models import (
    BusinessRule,
    BusinessRuleType,
    CRUDAnalysis,
    SpecAnalysis,
    StateMachine,
)

logger = logging.getLogger(__name__)


class DSLGenerator:
    """
    Generates DAZZLE DSL from specification analysis.

    Takes SpecAnalysis and user answers to questions, then generates
    complete DSL code including entities, surfaces, and documentation.
    """

    def __init__(self, analysis: SpecAnalysis, answers: dict[str, str] | None = None):
        """
        Initialize DSL generator.

        Args:
            analysis: SpecAnalysis from LLM
            answers: User answers to clarifying questions
        """
        self.analysis = analysis
        self.answers = answers or {}

    def generate(self, module_name: str = "app", app_name: str = "My App") -> str:
        """
        Generate complete DSL code.

        Args:
            module_name: Module name for DSL (e.g., "support_tickets")
            app_name: Human-readable app name

        Returns:
            Complete DSL code as string
        """
        lines = []

        # Header
        lines.extend(self._generate_header(module_name, app_name))
        lines.append("")

        # Entities
        lines.extend(self._generate_entities())
        lines.append("")

        # State machines (as documentation)
        if self.analysis.state_machines:
            lines.extend(self._generate_state_machine_docs())
            lines.append("")

        # Surfaces
        lines.extend(self._generate_surfaces())
        lines.append("")

        # Access control documentation
        if self.analysis.business_rules:
            lines.extend(self._generate_business_rules_docs())
            lines.append("")

        return "\n".join(lines)

    def _generate_header(self, module_name: str, app_name: str) -> list[str]:
        """Generate DSL header with module and app declarations."""
        lines = [
            "# Generated by DAZZLE LLM-assisted spec analysis",
            "# Review and customize as needed",
            "",
            f"module {module_name}",
            "",
            f'app {module_name} "{app_name}"',
        ]
        return lines

    def _generate_entities(self) -> list[str]:
        """Generate entity definitions from CRUD analysis."""
        lines = []
        lines.append(
            "# ============================================================================"
        )
        lines.append("# ENTITIES")
        lines.append(
            "# ============================================================================"
        )
        lines.append("")

        for crud in self.analysis.crud_analysis:
            lines.extend(self._generate_entity(crud))
            lines.append("")

        return lines

    def _generate_entity(self, crud: CRUDAnalysis) -> list[str]:
        """Generate a single entity definition."""
        lines = []
        entity_name = crud.entity

        lines.append(f'entity {entity_name} "{entity_name}":')

        # Infer fields from business rules and state machines
        fields = self._infer_entity_fields(entity_name)

        for field_name, field_def in fields.items():
            lines.append(f"  {field_name}: {field_def}")

        return lines

    def _infer_entity_fields(self, entity_name: str) -> dict[str, str]:
        """
        Infer entity fields from analysis.

        Uses state machines, business rules, and common patterns.
        """
        fields = {}

        # Standard ID field
        fields["id"] = "uuid pk"

        # Check for state machine fields
        for sm in self.analysis.state_machines:
            if sm.entity == entity_name:
                # Create enum field from states
                states_str = ",".join(sm.states)
                default_state = sm.states[0] if sm.states else "pending"
                fields[sm.field] = f"enum[{states_str}]={default_state}"

        # Check business rules for field hints
        for rule in self.analysis.business_rules:
            if rule.entity == entity_name and rule.field:
                field_name = rule.field

                if field_name in fields:
                    # Already have this field
                    continue

                # Infer type from rule
                if rule.type == BusinessRuleType.VALIDATION:
                    if "unique" in rule.rule.lower():
                        if "email" in field_name.lower():
                            fields[field_name] = "str(255) required unique"
                        else:
                            fields[field_name] = "str(200) required unique"
                    elif "required" in rule.rule.lower():
                        fields[field_name] = "str(200) required"
                    else:
                        # Generic string field
                        fields[field_name] = "str(200)"

        # Add common fields based on entity name and patterns
        # Title/Name field
        if entity_name.lower() in ["task", "ticket", "project", "item"]:
            if "title" not in fields:
                fields["title"] = "str(200) required"

        # Description field
        if "description" not in fields and entity_name.lower() != "comment":
            fields["description"] = "text"

        # Timestamps
        fields["created_at"] = "datetime auto_add"
        fields["updated_at"] = "datetime auto_update"

        return fields

    def _generate_state_machine_docs(self) -> list[str]:
        """Generate state machine documentation as comments."""
        lines = []
        lines.append(
            "# ============================================================================"
        )
        lines.append("# STATE MACHINES")
        lines.append(
            "# ============================================================================"
        )
        lines.append("#")
        lines.append("# The following state machines were identified in the specification.")
        lines.append("# Implement transitions in your backend code (views/services).")
        lines.append("")

        for sm in self.analysis.state_machines:
            lines.extend(self._generate_state_machine_doc(sm))
            lines.append("")

        return lines

    def _generate_state_machine_doc(self, sm: StateMachine) -> list[str]:
        """Generate documentation for a single state machine."""
        lines = []

        lines.append(f"# State Machine: {sm.entity}.{sm.field}")
        lines.append(f"# States: {', '.join(sm.states)}")
        lines.append("#")

        if sm.transitions_found:
            lines.append("# Transitions:")
            for trans in sm.transitions_found:
                lines.append(f"#   {trans.from_state} → {trans.to_state}")
                lines.append(f"#     Trigger: {trans.trigger}")
                if trans.who_can_trigger:
                    lines.append(f"#     Who: {trans.who_can_trigger}")
                if trans.conditions:
                    lines.append(f"#     Conditions: {', '.join(trans.conditions)}")
                if trans.side_effects:
                    lines.append(f"#     Side effects: {', '.join(trans.side_effects)}")

        if sm.transitions_implied_but_missing:
            lines.append("#")
            lines.append("# Missing transitions (need clarification):")
            for missing in sm.transitions_implied_but_missing:
                lines.append(f"#   ⚠ {missing.from_state} → {missing.to_state}")
                lines.append(f"#     Reason: {missing.reason}")

        return lines

    def _generate_surfaces(self) -> list[str]:
        """Generate surface definitions from CRUD analysis."""
        lines = []
        lines.append(
            "# ============================================================================"
        )
        lines.append("# SURFACES")
        lines.append(
            "# ============================================================================"
        )
        lines.append("")

        for crud in self.analysis.crud_analysis:
            lines.extend(self._generate_entity_surfaces(crud))
            lines.append("")

        return lines

    def _generate_entity_surfaces(self, crud: CRUDAnalysis) -> list[str]:
        """Generate all surfaces for an entity."""
        lines = []
        entity_name = crud.entity
        entity_name.lower()

        # Determine which surfaces to generate
        ops = crud.operations_mentioned

        # List surface
        list_op = ops.get("list")
        if list_op and list_op.found:
            lines.extend(self._generate_list_surface(entity_name))
            lines.append("")

        # Detail surface
        read_op = ops.get("read")
        if read_op and read_op.found:
            lines.extend(self._generate_detail_surface(entity_name))
            lines.append("")

        # Create surface
        create_op = ops.get("create")
        if create_op and create_op.found:
            lines.extend(self._generate_create_surface(entity_name))
            lines.append("")

        # Edit surface
        update_op = ops.get("update")
        if update_op and update_op.found:
            lines.extend(self._generate_edit_surface(entity_name))
            lines.append("")

        return lines

    def _generate_list_surface(self, entity_name: str) -> list[str]:
        """Generate list surface for entity."""
        entity_lower = entity_name.lower()
        lines = []

        lines.append(f'surface {entity_lower}_list "{entity_name} List":')
        lines.append(f"  uses entity {entity_name}")
        lines.append("  mode: list")
        lines.append("")
        lines.append(f'  section main "{entity_name}s":')

        # Add common fields
        fields = self._get_list_fields(entity_name)
        for field_name, field_label in fields:
            lines.append(f'    field {field_name} "{field_label}"')

        return lines

    def _generate_detail_surface(self, entity_name: str) -> list[str]:
        """Generate detail/view surface for entity."""
        entity_lower = entity_name.lower()
        lines = []

        lines.append(f'surface {entity_lower}_detail "{entity_name} Detail":')
        lines.append(f"  uses entity {entity_name}")
        lines.append("  mode: view")
        lines.append("")
        lines.append(f'  section main "{entity_name} Details":')

        # Add all fields for detail view
        fields = self._get_detail_fields(entity_name)
        for field_name, field_label in fields:
            lines.append(f'    field {field_name} "{field_label}"')

        return lines

    def _generate_create_surface(self, entity_name: str) -> list[str]:
        """Generate create surface for entity."""
        entity_lower = entity_name.lower()
        lines = []

        lines.append(f'surface {entity_lower}_create "New {entity_name}":')
        lines.append(f"  uses entity {entity_name}")
        lines.append("  mode: create")
        lines.append("")
        lines.append(f'  section main "Create {entity_name}":')

        # Add editable fields (exclude auto fields)
        fields = self._get_create_fields(entity_name)
        for field_name, field_label in fields:
            lines.append(f'    field {field_name} "{field_label}"')

        return lines

    def _generate_edit_surface(self, entity_name: str) -> list[str]:
        """Generate edit surface for entity."""
        entity_lower = entity_name.lower()
        lines = []

        lines.append(f'surface {entity_lower}_edit "Edit {entity_name}":')
        lines.append(f"  uses entity {entity_name}")
        lines.append("  mode: edit")
        lines.append("")
        lines.append(f'  section main "Edit {entity_name}":')

        # Add editable fields
        fields = self._get_edit_fields(entity_name)
        for field_name, field_label in fields:
            lines.append(f'    field {field_name} "{field_label}"')

        return lines

    def _get_list_fields(self, entity_name: str) -> list[tuple[str, str]]:
        """Get fields to show in list view."""
        fields = []

        # Common patterns
        if entity_name.lower() in ["task", "ticket", "project", "item"]:
            fields.append(("title", "Title"))

        # Check for state machine field
        for sm in self.analysis.state_machines:
            if sm.entity == entity_name:
                field_label = sm.field.replace("_", " ").title()
                fields.append((sm.field, field_label))

        # Always add created_at
        fields.append(("created_at", "Created"))

        return fields

    def _get_detail_fields(self, entity_name: str) -> list[tuple[str, str]]:
        """Get fields to show in detail view."""
        fields = []

        # Show all fields in detail
        if entity_name.lower() in ["task", "ticket", "project", "item"]:
            fields.append(("title", "Title"))
            fields.append(("description", "Description"))

        # Check for state machine field
        for sm in self.analysis.state_machines:
            if sm.entity == entity_name:
                field_label = sm.field.replace("_", " ").title()
                fields.append((sm.field, field_label))

        fields.append(("created_at", "Created"))
        fields.append(("updated_at", "Last Updated"))

        return fields

    def _get_create_fields(self, entity_name: str) -> list[tuple[str, str]]:
        """Get fields for create form."""
        fields = []

        if entity_name.lower() in ["task", "ticket", "project", "item"]:
            fields.append(("title", "Title"))
            fields.append(("description", "Description"))

        # Status field if it exists (but not for create - use default)
        # Priority field
        if entity_name.lower() == "task":
            fields.append(("priority", "Priority"))

        return fields

    def _get_edit_fields(self, entity_name: str) -> list[tuple[str, str]]:
        """Get fields for edit form."""
        fields = []

        if entity_name.lower() in ["task", "ticket", "project", "item"]:
            fields.append(("title", "Title"))
            fields.append(("description", "Description"))

        # Check for state machine field
        for sm in self.analysis.state_machines:
            if sm.entity == entity_name:
                field_label = sm.field.replace("_", " ").title()
                fields.append((sm.field, field_label))

        # Priority field
        if entity_name.lower() == "task":
            fields.append(("priority", "Priority"))

        return fields

    def _generate_business_rules_docs(self) -> list[str]:
        """Generate business rules documentation."""
        lines = []
        lines.append(
            "# ============================================================================"
        )
        lines.append("# BUSINESS RULES")
        lines.append(
            "# ============================================================================"
        )
        lines.append("#")
        lines.append("# The following business rules were identified:")
        lines.append("")

        # Group by type
        rules_by_type: dict[BusinessRuleType, list[BusinessRule]] = {}
        for rule in self.analysis.business_rules:
            if rule.type not in rules_by_type:
                rules_by_type[rule.type] = []
            rules_by_type[rule.type].append(rule)

        for rule_type, rules in rules_by_type.items():
            lines.append(f"# {rule_type.value.upper()}:")
            for rule in rules:
                entity_field = f"{rule.entity}.{rule.field}" if rule.field else rule.entity
                lines.append(f"#   • {entity_field}: {rule.rule}")
            lines.append("#")

        return lines


def generate_dsl_from_analysis(
    analysis: SpecAnalysis,
    answers: dict[str, str],
    output_path: Path,
    module_name: str = "app",
    app_name: str = "My App",
) -> None:
    """
    Convenience function to generate and write DSL file.

    Args:
        analysis: SpecAnalysis from LLM
        answers: User answers to questions
        output_path: Where to write the DSL file
        module_name: Module name
        app_name: App name
    """
    generator = DSLGenerator(analysis, answers)
    dsl_code = generator.generate(module_name, app_name)

    output_path.write_text(dsl_code)
    logger.info(f"Generated DSL written to {output_path}")
