"""Template creation modal for creating new templates.

Provides a comprehensive modal screen that allows users to create a new template
with all required fields, external editor integration, and macro resolution.
"""

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from textual import on, work
from textual.app import ComposeResult
from textual.containers import Horizontal, VerticalScroll
from textual.widgets import (
    Button,
    Checkbox,
    Input,
    Label,
    RadioButton,
    RadioSet,
    Static,
)

from cli.infrastructure.scope_utils import get_scope_from_radioset
from cli.tui.widgets.base_form_modal import BaseFormModal

from alloy_runtime_sdk.api_client.client import ApiClient
from alloy_runtime_types.dtos.templates import CreateTemplateRequest
from alloy_runtime_types.enums.ownership_scope import OwnershipScope
from alloy_runtime_types.enums.template_enums import (
    TemplateContentType,
    TemplateVisibility,
)

from cli.infrastructure.editor import EditorError, open_editor_with_hints
from cli.infrastructure.forms.resolution_modal import ResolutionModal
from cli.infrastructure.forms.tag_picker import TagPicker
from cli.infrastructure.macro_parser import (
    MacroMatch,
    build_jinja_replacement,
    compile_content,
    deduplicate_macros,
    parse_macros,
)

if TYPE_CHECKING:
    from cli.tui.app import AlloyRuntimeApp


@dataclass
class TemplateCreateResult:
    """Result of successful template creation."""

    template_id: str
    template_name: str
    version_id: str
    version_number: int


class TemplateCreateModal(BaseFormModal[TemplateCreateResult]):
    """Modal for creating a new template.

    Provides a comprehensive form with:
    - Required fields: name, content type, visibility, tags
    - External editor integration for content
    - Macro resolution flow for @fragment, @text, @json, @schema
    - Optional fields: description, upsert, escape_jinja

    Usage:
        def on_template_created(result: TemplateCreateResult | None):
            if result is not None:
                # Template was created successfully
                self.notify(f"Created template: {result.template_name}")
                self._do_search("")  # Refresh list

        client = await self.app.store.get_client()
        self.app.push_screen(
            TemplateCreateModal(client=client),
            on_template_created
        )
    """

    app: "AlloyRuntimeApp"

    def __init__(
        self,
        client: ApiClient,
        **kwargs: Any,
    ) -> None:
        """Initialize the template creation modal.

        Args:
            client: ApiClient instance for API calls
        """
        super().__init__(**kwargs)
        self._client = client

        # Template content state
        self._content: str = ""
        self._pending_macros: list[MacroMatch] = []  # Unique macros to resolve
        self._all_macros: list[MacroMatch] = []  # All macros for replacement mapping
        self._replacements: dict[str, str] = {}
        self._current_macro_index: int = 0
        self._macros_resolved: bool = False

    def compose(self) -> ComposeResult:
        with VerticalScroll(id="template-create-modal"):
            yield Label("Create New Template", classes="modal-title")

            # ===== REQUIRED FIELDS =====
            yield Static("[bold]Required[/]", classes="form-section")

            yield Static(
                "Name (lowercase, dots/dashes/underscores)", classes="field-label"
            )
            yield Input(
                placeholder="e.g., email.welcome-header, system.default_prompt",
                id="name-input",
            )

            yield Static("Content Type", classes="field-label")
            with RadioSet(id="content-type-radios"):
                yield RadioButton("System Instruction", id="type-system", value=True)
                yield RadioButton("User Message", id="type-user")
                yield RadioButton("Fragment", id="type-fragment")

            yield Static("Visibility", classes="field-label")
            with RadioSet(id="visibility-radios"):
                yield RadioButton("Private", id="vis-private")
                yield RadioButton("Organization", id="vis-org", value=True)
                yield RadioButton("Public", id="vis-public")

            yield Static("Ownership Scope", classes="field-label")
            with RadioSet(id="scope-radios"):
                yield RadioButton("User (personal)", id="scope-user")
                yield RadioButton("Organization", id="scope-org", value=True)
                yield RadioButton("Global", id="scope-global")

            # ===== TEMPLATE CONTENT =====
            yield Static("[bold]Template Content[/]", classes="form-section")

            yield Static(
                "No content yet. Click 'Edit Content' to open your editor.",
                id="content-status",
                classes="content-status no-content",
            )

            with Horizontal(classes="content-buttons"):
                yield Button(
                    "Edit Content",
                    id="edit-content-btn",
                    variant="primary",
                )
                yield Button(
                    "Resolve Macros",
                    id="resolve-macros-btn",
                    variant="warning",
                    disabled=True,
                )

            yield Static("Tags (required)", classes="field-label")
            yield TagPicker(
                client=self._client,
                placeholder="Search tags...",
                empty_message="No tags selected (required)",
                id="tag-picker",
            )

            # ===== OPTIONAL FIELDS =====
            yield Static("[bold]Optional[/]", classes="form-section")

            yield Static("Description", classes="field-label")
            yield Input(
                placeholder="Brief description of this template's purpose",
                id="description-input",
            )

            with Horizontal(classes="checkbox-row"):
                yield Checkbox(
                    "Return existing if name exists (upsert)",
                    id="upsert-checkbox",
                )

            with Horizontal(classes="checkbox-row"):
                yield Checkbox(
                    "Escape Jinja syntax ({{, {%, {#)",
                    id="escape-jinja-checkbox",
                )

            # Error display
            yield Static("", id="error-display", classes="error-display")

            with Horizontal(classes="button-row"):
                yield Button("Create", id="create-btn", variant="success")
                yield Button("Cancel", id="cancel-btn", variant="default")

    def on_mount(self) -> None:
        """Focus the name input on mount."""
        self.query_one("#name-input", Input).focus()

    @on(Button.Pressed, "#edit-content-btn")
    def on_edit_content_pressed(self, event: Button.Pressed) -> None:
        """Open the external editor for content editing."""
        with self.app.suspend():
            try:
                content = open_editor_with_hints(self._content)
                if content is not None:
                    self._content = content
                    # Reset resolution state when content changes
                    self._macros_resolved = False
                    self._replacements = {}
                    self._update_content_status()
            except EditorError as e:
                self._show_error(str(e))

    @on(Button.Pressed, "#resolve-macros-btn")
    def on_resolve_macros_pressed(self, event: Button.Pressed) -> None:
        """Start the macro resolution flow."""
        self._start_macro_resolution()

    @on(Button.Pressed, "#create-btn")
    def on_create_pressed(self, event: Button.Pressed) -> None:
        """Handle create button press."""
        self.action_submit()

    @on(Button.Pressed, "#cancel-btn")
    def on_cancel_pressed(self, event: Button.Pressed) -> None:
        """Handle cancel button press."""
        self.dismiss(None)

    def action_submit(self) -> None:
        """Handle form submission."""
        try:
            self._validate_fields()

            # Check if there are unresolved macros
            macros = parse_macros(self._content)
            if macros and not self._macros_resolved:
                self._show_error(
                    f"Found {len(macros)} unresolved macro(s). "
                    "Click 'Resolve Macros' first, or remove the macros from your content."
                )
                return

            # Clear any previous error
            self._hide_error()

            # Submit to API
            self._finalize_submission()

        except ValueError as e:
            self._show_error(str(e))

    def _validate_fields(self) -> None:
        """Validate all required fields."""
        import re

        name = self.query_one("#name-input", Input).value.strip()
        if not name:
            raise ValueError("Name is required")

        if not re.match(r"^[a-z0-9._-]+$", name):
            raise ValueError(
                "Name must be lowercase alphanumeric with dots, dashes, or underscores only"
            )

        if not self._content.strip():
            raise ValueError(
                "Template content is required. Click 'Edit Content' to add content."
            )

        tag_picker = self.query_one("#tag-picker", TagPicker)
        tag_paths = tag_picker.selected_tags
        if not tag_paths:
            raise ValueError("At least one tag is required")

    def _update_content_status(self) -> None:
        """Update the content status display and resolve button state."""
        status = self.query_one("#content-status", Static)
        resolve_btn = self.query_one("#resolve-macros-btn", Button)

        if not self._content.strip():
            status.update("No content yet. Click 'Edit Content' to open your editor.")
            status.remove_class("has-content", "has-macros", "macros-resolved")
            status.add_class("no-content")
            resolve_btn.disabled = True
            return

        # Parse macros from content
        all_macros = parse_macros(self._content)
        unique_macros = deduplicate_macros(all_macros)
        char_count = len(self._content)
        line_count = self._content.count("\n") + 1

        if unique_macros:
            if self._macros_resolved:
                # Macros have been resolved
                resolved_keys = {
                    (m.macro_type, m.query)
                    for m in all_macros
                    if m.original_text in self._replacements
                }
                resolved_count = len(resolved_keys)
                skipped_count = len(unique_macros) - resolved_count

                total_instances = len(all_macros)
                status_text = (
                    f"Content: {char_count} chars, {line_count} lines\n"
                    f"Macros resolved: {resolved_count}/{len(unique_macros)} unique"
                )
                if total_instances > len(unique_macros):
                    status_text += f" ({total_instances} total instances)"
                if skipped_count > 0:
                    status_text += f"\n({skipped_count} skipped - will remain as-is)"
                status.update(status_text)
                status.remove_class("no-content", "has-content", "has-macros")
                status.add_class("macros-resolved")
                resolve_btn.disabled = False
                resolve_btn.label = "Re-resolve Macros"
            else:
                # Macros found but not yet resolved
                macro_summary = ", ".join(
                    f"@{m.macro_type}({m.query})" for m in unique_macros
                )

                total_instances = len(all_macros)
                instance_note = ""
                if total_instances > len(unique_macros):
                    instance_note = f" ({total_instances} total instances)"

                status.update(
                    f"Content: {char_count} chars, {line_count} lines\n"
                    f"Macros found: {len(unique_macros)} unique{instance_note} - {macro_summary}\n"
                    f"[bold]Click 'Resolve Macros' before submitting[/bold]"
                )
                status.remove_class("no-content", "has-content", "macros-resolved")
                status.add_class("has-macros")
                resolve_btn.disabled = False
                resolve_btn.label = "Resolve Macros"
        else:
            status.update(f"Content ready: {char_count} chars, {line_count} lines")
            status.remove_class("no-content", "has-macros", "macros-resolved")
            status.add_class("has-content")
            resolve_btn.disabled = True

    def _start_macro_resolution(self) -> None:
        """Start the macro resolution flow."""
        self._all_macros = parse_macros(self._content)
        self._pending_macros = deduplicate_macros(self._all_macros)
        self._replacements = {}
        self._current_macro_index = 0
        self._macros_resolved = False

        if self._pending_macros:
            self._process_next_macro()
        else:
            self.app.notify("No macros to resolve", severity="information")

    def _process_next_macro(self) -> None:
        """Process the next macro in the queue."""
        if self._current_macro_index >= len(self._pending_macros):
            # All macros processed
            self._macros_resolved = True
            self._update_content_status()

            resolved_keys = {
                (m.macro_type, m.query)
                for m in self._all_macros
                if m.original_text in self._replacements
            }
            resolved_count = len(resolved_keys)
            total_unique = len(self._pending_macros)
            total_instances = len(self._all_macros)

            if total_instances > total_unique:
                self.app.notify(
                    f"Resolution complete: {resolved_count}/{total_unique} unique macros resolved ({total_instances} instances)",
                    severity="information",
                )
            else:
                self.app.notify(
                    f"Resolution complete: {resolved_count}/{total_unique} macros resolved",
                    severity="information",
                )
            return

        macro = self._pending_macros[self._current_macro_index]

        # Open resolution modal for this macro
        self.app.push_screen(
            ResolutionModal(
                client=self._client,
                macro_type=macro.macro_type,
                query=macro.query,
            ),
            self._on_macro_resolved,
        )

    def _on_macro_resolved(self, result: dict[str, Any] | None) -> None:
        """Callback when a macro resolution modal is dismissed."""
        macro = self._pending_macros[self._current_macro_index]

        if result is not None:
            # User selected a value
            resolved_value = result["value"]
            jinja_replacement = build_jinja_replacement(
                macro.macro_type, resolved_value
            )

            # Find all macros with the same (macro_type, query) and add replacements
            matching_count = 0
            for m in self._all_macros:
                if m.macro_type == macro.macro_type and m.query == macro.query:
                    self._replacements[m.original_text] = jinja_replacement
                    matching_count += 1

            if matching_count > 1:
                self.app.notify(
                    f"Resolved {matching_count} instances of @{macro.macro_type}({macro.query}) -> {result['display']}",
                    severity="information",
                )
            else:
                self.app.notify(
                    f"Resolved {macro.original_text} -> {result['display']}",
                    severity="information",
                )
        else:
            # User skipped this macro
            self.app.notify(
                f"Skipped resolution for {macro.original_text}",
                severity="warning",
            )

        # Move to next macro
        self._current_macro_index += 1
        self._process_next_macro()

    @work(exclusive=True)
    async def _finalize_submission(self) -> None:
        """Compile content and submit to API."""
        try:
            # Apply all replacements
            final_content = compile_content(self._content, self._replacements)

            # Build the request
            request = self._build_request(final_content)

            # Submit to API
            response = await self._client.create_template(request)

            # Return success result
            result = TemplateCreateResult(
                template_id=str(response.template.id),
                template_name=response.template.name,
                version_id=str(response.version.id),
                version_number=response.version.version,
            )
            self.dismiss(result)

        except Exception as e:
            self._show_error(f"Failed to create template: {e}")

    # Template-specific RadioSet parsers (not scope-related)
    def _get_content_type(self) -> TemplateContentType:
        """Get content type from RadioSet."""
        radios = self.query_one("#content-type-radios", RadioSet)
        pressed = radios.pressed_button
        if pressed is None or pressed.id == "type-system":
            return TemplateContentType.SYSTEM_INSTRUCTION
        elif pressed.id == "type-user":
            return TemplateContentType.USER_MESSAGE
        else:
            return TemplateContentType.FRAGMENT

    def _get_visibility(self) -> TemplateVisibility:
        """Get visibility from RadioSet."""
        radios = self.query_one("#visibility-radios", RadioSet)
        pressed = radios.pressed_button
        if pressed is None or pressed.id == "vis-org":
            return TemplateVisibility.ORGANIZATION
        elif pressed.id == "vis-private":
            return TemplateVisibility.PRIVATE
        else:
            return TemplateVisibility.PUBLIC

    def _build_request(self, final_content: str) -> CreateTemplateRequest:
        """Build the API request."""
        name = self.query_one("#name-input", Input).value.strip()
        description = self.query_one("#description-input", Input).value.strip() or None

        tag_picker = self.query_one("#tag-picker", TagPicker)
        tag_paths = tag_picker.selected_tags

        upsert = self.query_one("#upsert-checkbox", Checkbox).value
        escape_jinja = self.query_one("#escape-jinja-checkbox", Checkbox).value

        return CreateTemplateRequest(
            name=name,
            content_type=self._get_content_type(),
            visibility=self._get_visibility(),
            initial_content=final_content,
            tag_paths=tag_paths,
            description=description,
            upsert=upsert,
            scope=self._get_scope(),
            escape_jinja=escape_jinja,
        )

    def _get_scope(self) -> OwnershipScope:
        return get_scope_from_radioset(self.query_one("#scope-radios", RadioSet))
