"""Form containers for genui (Django ORM-style).

Forms are defined by subclassing Form or MultiStepForm and declaring
fields/steps as class attributes. Metadata (title, description, etc.) is
passed when instantiating.

Confirmation is a lightweight instance-based container for yes/no decisions.
"""

import json
from collections import OrderedDict
from typing import Any, Dict, List, Optional, Type, TYPE_CHECKING

from .fields import Field, FileField


class ValidationResult:
    """Result of validating a form submission.

    Attributes:
        is_valid: True if all fields passed validation.
        errors: Dict mapping field name → error message for invalid fields.
    """

    __slots__ = ("is_valid", "errors")

    def __init__(self, errors: Dict[str, str]):
        self.errors = errors
        self.is_valid = len(errors) == 0

    def to_dict(self) -> Dict[str, Any]:
        return {"is_valid": self.is_valid, "errors": self.errors}

    def __repr__(self) -> str:
        if self.is_valid:
            return "ValidationResult(valid)"
        return f"ValidationResult(errors={self.errors!r})"

if TYPE_CHECKING:
    from ..attachment_client import AttachmentClient
    from ..message import Message


class Step:
    """A step in a multi-step form. Declare fields as class attributes.

    Define a Step subclass with fields, then instantiate with metadata:

        class Demographics(Step):
            name = Text(label="Full Name")
            age = Number(label="Age", min=0, max=150)

        class PainAssessment(Step):
            pain_level = Slider(label="Pain Level", min=0, max=10)

        class PatientIntake(MultiStepForm):
            demographics = Demographics(label="Basic Info")
            pain = PainAssessment(label="Pain Assessment")
    """

    _fields: Dict[str, Field]
    _name: str = ""

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        fields = OrderedDict()
        for attr_name, attr_value in list(cls.__dict__.items()):
            if isinstance(attr_value, Field):
                attr_value._name = attr_name
                fields[attr_name] = attr_value
        cls._fields = fields

    def __init__(
        self,
        *,
        label: str = "",
        description: Optional[str] = None,
    ):
        self._label = label
        self._description = description

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {
            "id": self._name,
            "label": self._label or self.__class__.__name__,
            "fields": [f.to_dict() for f in self.__class__._fields.values()],
        }
        if self._description is not None:
            d["description"] = self._description
        return d


class Form:
    """Single-step form. Declare fields as class attributes.

    Metadata is passed when instantiating the form.

    Example:
        class SymptomReport(Form):
            name = Text(label="Full Name", placeholder="John Doe")
            severity = Slider(label="Pain Level", min=1, max=10)

    Sending:
        form = SymptomReport(title="Symptom Report", description="Describe")
        streamer.set_response_schema(form)

    Receiving:
        data = await SymptomReport.parse(message)
        data.name       # str
        data.severity   # float
    """

    _fields: Dict[str, Field]

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        fields = OrderedDict()
        for attr_name, attr_value in list(cls.__dict__.items()):
            if isinstance(attr_value, Field):
                attr_value._name = attr_name
                fields[attr_name] = attr_value
        cls._fields = fields

    def __init__(
        self,
        *,
        title: str = "",
        description: Optional[str] = None,
        submit_label: str = "Submit",
        cancel_label: str = "Cancel",
        allow_cancel: bool = False,
    ):
        self._title = title
        self._description = description
        self._submit_label = submit_label
        self._cancel_label = cancel_label
        self._allow_cancel = allow_cancel

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {
            "type": "form",
            "title": self._title,
            "fields": [f.to_dict() for f in self.__class__._fields.values()],
            "submit_label": self._submit_label,
            "allow_cancel": self._allow_cancel,
        }
        if self._description is not None:
            d["description"] = self._description
        if self._allow_cancel:
            d["cancel_label"] = self._cancel_label
        return d

    @classmethod
    async def parse(
        cls,
        source: Any,
        *,
        attachment_client: Optional["AttachmentClient"] = None,
    ):
        """Parse form submission into a typed instance.

        Args:
            source: A Message object, a JSON string, or a dict.
                    If Message, extracts .content and ._attachment_client.
            attachment_client: Optional, for FileField resolution.
                              Overrides the one from Message if provided.

        Returns:
            An instance of this Form subclass with parsed field values.
        """
        data, attachment_client = _extract_source(source, attachment_client)
        instance = object.__new__(cls)

        for field_name, field_obj in cls._fields.items():
            raw = data.get(field_name)
            if isinstance(field_obj, FileField) and attachment_client is not None:
                parsed = await field_obj.async_parse(raw, attachment_client)
            else:
                parsed = field_obj.parse(raw)
            setattr(instance, field_name, parsed)

        return instance

    @classmethod
    def validate(cls, instance) -> "ValidationResult":
        """Validate a parsed form instance.

        Args:
            instance: A parsed Form instance (from cls.parse()).

        Returns:
            A ValidationResult with per-field errors (if any).
        """
        errors: Dict[str, str] = {}
        for field_name, field_obj in cls._fields.items():
            value = getattr(instance, field_name, None)
            error = field_obj.validate(value)
            if error:
                errors[field_name] = error
        return ValidationResult(errors)

    def __repr__(self) -> str:
        field_values = ", ".join(
            f"{name}={getattr(self, name, '?')!r}"
            for name in self.__class__._fields
        )
        return f"{self.__class__.__name__}({field_values})"


class MultiStepForm:
    """Multi-step form. Steps are Step instances as class attributes.

    Metadata is passed when instantiating the form.

    Example:
        class Demographics(Step):
            name = Text(label="Full Name")
            age = Number(label="Age", min=0, max=150)

        class PainAssessment(Step):
            pain_level = Slider(label="Pain Level", min=0, max=10)

        class PatientIntake(MultiStepForm):
            demographics = Demographics(label="Basic Info")
            pain = PainAssessment(label="Pain Assessment")

        form = PatientIntake(title="Patient Intake")
        streamer.set_response_schema(form)
    """

    _steps: Dict[str, Step]
    _fields: Dict[str, Field]

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        steps = OrderedDict()
        all_fields = OrderedDict()
        for attr_name, attr_value in list(cls.__dict__.items()):
            if isinstance(attr_value, Step):
                attr_value._name = attr_name
                steps[attr_name] = attr_value
                for field_name, field_obj in attr_value.__class__._fields.items():
                    if field_name in all_fields:
                        raise ValueError(
                            f"Duplicate field name '{field_name}' across steps"
                        )
                    all_fields[field_name] = field_obj
        cls._steps = steps
        cls._fields = all_fields

    def __init__(
        self,
        *,
        title: str = "",
        description: Optional[str] = None,
        submit_label: str = "Submit",
        cancel_label: str = "Cancel",
        allow_cancel: bool = False,
    ):
        self._title = title
        self._description = description
        self._submit_label = submit_label
        self._cancel_label = cancel_label
        self._allow_cancel = allow_cancel

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {
            "type": "multi_step_form",
            "title": self._title,
            "steps": [
                step_instance.to_dict()
                for step_instance in self.__class__._steps.values()
            ],
            "submit_label": self._submit_label,
            "allow_cancel": self._allow_cancel,
        }
        if self._description is not None:
            d["description"] = self._description
        if self._allow_cancel:
            d["cancel_label"] = self._cancel_label
        return d

    @classmethod
    async def parse(
        cls,
        source: Any,
        *,
        attachment_client: Optional["AttachmentClient"] = None,
    ):
        """Parse multi-step form submission. Fields are flattened across steps."""
        data, attachment_client = _extract_source(source, attachment_client)
        instance = object.__new__(cls)

        for field_name, field_obj in cls._fields.items():
            raw = data.get(field_name)
            if isinstance(field_obj, FileField) and attachment_client is not None:
                parsed = await field_obj.async_parse(raw, attachment_client)
            else:
                parsed = field_obj.parse(raw)
            setattr(instance, field_name, parsed)

        return instance

    @classmethod
    def validate(cls, instance) -> "ValidationResult":
        """Validate a parsed multi-step form instance.

        Args:
            instance: A parsed MultiStepForm instance (from cls.parse()).

        Returns:
            A ValidationResult with per-field errors (if any).
        """
        errors: Dict[str, str] = {}
        for field_name, field_obj in cls._fields.items():
            value = getattr(instance, field_name, None)
            error = field_obj.validate(value)
            if error:
                errors[field_name] = error
        return ValidationResult(errors)

    def __repr__(self) -> str:
        field_values = ", ".join(
            f"{name}={getattr(self, name, '?')!r}"
            for name in self.__class__._fields
        )
        return f"{self.__class__.__name__}({field_values})"


class Confirmation:
    """Confirmation card — presents a yes/no decision with no input fields.

    Create an instance directly (no subclassing needed).

    Example:
        confirm = Confirmation(
            title="Delete Account",
            message="Are you sure? This cannot be undone.",
            confirm_label="Delete",
            cancel_label="Keep Account",
            variant="destructive",
        )

    Sending:
        streamer.set_response_schema(confirm)

    Receiving:
        result = await Confirmation.parse(message)
        result.confirmed  # bool
    """

    def __init__(
        self,
        *,
        title: str,
        message: str,
        description: Optional[str] = None,
        confirm_label: str = "Confirm",
        cancel_label: str = "Cancel",
        variant: str = "default",
    ):
        self.title = title
        self.message = message
        self.description = description
        self.confirm_label = confirm_label
        self.cancel_label = cancel_label
        self.variant = variant
        self.confirmed: bool = False

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {
            "type": "confirmation",
            "title": self.title,
            "message": self.message,
            "confirm_label": self.confirm_label,
            "cancel_label": self.cancel_label,
            "variant": self.variant,
        }
        if self.description is not None:
            d["description"] = self.description
        return d

    @classmethod
    async def parse(cls, source: Any) -> "Confirmation":
        """Parse confirmation response.

        Args:
            source: A Message object, a JSON string, or a dict.
                    Expected shape: {"confirmed": true/false}

        Returns:
            A Confirmation instance with .confirmed set.
        """
        data, _ = _extract_source(source, None)
        instance = object.__new__(cls)
        instance.confirmed = bool(data.get("confirmed", False))
        return instance

    def __repr__(self) -> str:
        return f"Confirmation(confirmed={self.confirmed!r})"


def _extract_source(source: Any, attachment_client):
    """Extract data dict and attachment_client from source."""
    # Lazy import to avoid circular dependency
    from ..message import Message

    if isinstance(source, Message):
        content = source.content
        if attachment_client is None:
            attachment_client = source._attachment_client
    elif isinstance(source, dict):
        content = source
    elif isinstance(source, str):
        content = source
    else:
        raise TypeError(
            f"Expected Message, str, or dict, got {type(source).__name__}"
        )

    if isinstance(content, str):
        data = json.loads(content)
    else:
        data = content

    return data, attachment_client
