"""Type definitions for structured test case generation.

Step and TestCase schemas use the same field names as Mirabilis component
records so that generated test files can be pushed and executed by Mirabilis
without any field-name translation.
"""

from typing import Any
from enum import Enum

from pydantic import BaseModel, Field


# ---------------------------------------------------------------------------
# Mirabilis-compatible step schema
# ---------------------------------------------------------------------------

class ActionType(str, Enum):
    GO_TO = "go_to"
    CLICK = "click"
    SEND_KEYS = "send_keys"
    HOVER = "hover"
    UPLOAD = "upload"
    SCREENSHOT = "screenshot"
    SELECT = "select"
    PRESS = "press"


class SelectorType(str, Enum):
    CSS = "css"
    XPATH = "xpath"
    TEXT = "text"
    ROLE = "role"
    LABEL = "label"
    PLACEHOLDER = "placeholder"
    TESTID = "testid"


class TestStep(BaseModel):
    """A single action or assertion step — identical to a Mirabilis component record.

    Action steps have action_taken set; assertion steps have assertion_type set.
    Both can coexist on the same step (e.g. click then immediately verify).
    """

    # ── Selector fields ───────────────────────────────────────────────────────
    # Format: [attribute="value"]  — e.g. [id="email"], [data-testid="submit"]
    css_selector: str | None = None
    # Absolute XPath — e.g. /html/body/div[1]/form[1]/input[2]  (1-indexed siblings)
    xpath: str | None = None
    # 0-indexed occurrence when css_selector matches multiple elements
    nth_appearance: int = 0
    # HTML tag name: "input", "button", "a", "select", "textarea" …
    tag: str | None = None

    # ── Action fields ─────────────────────────────────────────────────────────
    # "click" | "send_keys" | "hover" | "go_to" | "set_range" | "check" | "click_select"
    action_taken: str = ""
    # Value to type (send_keys) or URL to navigate to (go_to)
    mock_input: str | None = None
    # "file" | "checkbox" | "radio" | "dropdown" — only when relevant
    validation_type: str | None = None
    # Dropdown option texts (used for select elements)
    options: list[str] = Field(default_factory=list)

    # ── Assertion fields ──────────────────────────────────────────────────────
    # Exact Mirabilis assertion type strings — see ASSERTION_TYPES below
    assertion_type: str | None = None
    assertion_value: str | None = None
    # "existence" | "input" | "page"
    assertion_nature: str | None = None

    # ── Timing ────────────────────────────────────────────────────────────────
    wait_before: int = 0   # ms to wait before executing this step
    wait_after: int = 0    # ms to wait after executing this step

    # ── Metadata ──────────────────────────────────────────────────────────────
    description: str = ""

    class Config:
        populate_by_name = True

    # ------------------------------------------------------------------
    # Back-compat helpers for tools that expect normalized step fields.
    # ------------------------------------------------------------------
    @property
    def action(self) -> ActionType | None:
        if self.action_taken:
            try:
                return ActionType(self.action_taken)
            except Exception:
                return None
        return None

    @property
    def selector(self) -> str | None:
        return self.css_selector or self.xpath

    @property
    def selector_type(self) -> SelectorType | None:
        if self.css_selector:
            return SelectorType.CSS
        if self.xpath:
            return SelectorType.XPATH
        return None

    @property
    def value(self) -> str | None:
        return self.mock_input

    @property
    def url(self) -> str | None:
        return self.mock_input if self.action == ActionType.GO_TO else None

    @property
    def timeout(self) -> int | None:
        return None

    @property
    def condition(self) -> str | None:
        return None


# ---------------------------------------------------------------------------
# Allowed assertion type strings (must match Mirabilis dispatch exactly)
# ---------------------------------------------------------------------------

EXISTENCE_ASSERTION_TYPES: tuple[str, ...] = (
    "to be visible",
    "not to be visible",
)

INPUT_ASSERTION_TYPES: tuple[str, ...] = (
    "to be visible",
    "to be hidden",
    "to be enabled",
    "to be disabled",
    "to be editable",
    "to be focused",
    "to be empty",
    "to be checked",
    "to be not checked",
    "to be selected",
    "not to be selected",
    "to have value",
    "to contain value",
    "to have text",
    "to contain text",
    "to be password",
    "to be email",
    "to be number",
    "to be date",
    "has option",
    "assert all options",
    "to have default option",
)

PAGE_ASSERTION_TYPES: tuple[str, ...] = (
    "to have title",
    "to have url",
    "to contain url",
)

ALL_ASSERTION_TYPES: tuple[str, ...] = (
    *EXISTENCE_ASSERTION_TYPES,
    *INPUT_ASSERTION_TYPES,
    *PAGE_ASSERTION_TYPES,
)


# ---------------------------------------------------------------------------
# Test case
# ---------------------------------------------------------------------------

class AssertionType(str, Enum):
    # Core visibility/existence checks
    VISIBLE = "visible"
    HIDDEN = "hidden"
    ELEMENT_EXISTS = "element_exists"
    ELEMENT_NOT_EXISTS = "element_not_exists"
    # Input / state checks
    ENABLED = "enabled"
    DISABLED = "disabled"
    INPUT_EMPTY = "input_empty"
    # Collection / API checks
    ELEMENT_COUNT_LESS = "element_count_less"
    API_NOT_CALLED = "api_not_called"


class TestAssertion(BaseModel):
    type: AssertionType
    description: str = ""
    selector: str | None = None
    expected: str | None = None
    timeout: int | None = None
    attribute: str | None = None
    api_endpoint: str | None = None

    class Config:
        populate_by_name = True


class TestCase(BaseModel):
    """A complete test case with interleaved action and assertion steps.

    Steps are ordered exactly as a user would perform them — no separate
    assertions array.  This matches how Mirabilis stores and executes components.
    """

    name: str
    description: str = ""
    priority: str = "medium"   # "smoke" | "critical" | "high" | "medium" | "low"
    tags: list[str] = Field(default_factory=list)
    steps: list[TestStep]
    assertions: list[TestAssertion] = Field(default_factory=list)

    class Config:
        populate_by_name = True


# ---------------------------------------------------------------------------
# Scan-phase models (unchanged — used during repo scanning)
# ---------------------------------------------------------------------------

class ScannedRoute(BaseModel):
    path: str
    component: str | None = None
    auth_required: bool = False
    redirects_to: str | None = None


class ScannedFormField(BaseModel):
    selector: str
    selector_type: str
    field_type: str
    required: bool = False
    validation_rule: str | None = None


class ScannedForm(BaseModel):
    page: str
    form_name: str
    fields: list[ScannedFormField] = Field(default_factory=list)
    submit_selector: str | None = None
    submit_method: str = "click"
    success_indicator: str | None = None
    error_messages: list[str] = Field(default_factory=list)


class ScannedNavEdge(BaseModel):
    from_page: str
    to_page: str
    trigger: str
    selector: str | None = None


class ScannedAuthFlow(BaseModel):
    login_url: str
    email_selector: str
    password_selector: str
    submit_selector: str
    post_login_steps: list[str] = Field(default_factory=list)
    protected_routes: list[str] = Field(default_factory=list)


class ScannedConditionalUI(BaseModel):
    element_description: str
    selector: str | None = None
    visible_when: str
    trigger_action: str


class StructuredScanNotes(BaseModel):
    """Structured scan notes persisted across generation batches."""

    routes: list[ScannedRoute] = Field(default_factory=list)
    forms: list[ScannedForm] = Field(default_factory=list)
    nav_graph: list[ScannedNavEdge] = Field(default_factory=list)
    auth: ScannedAuthFlow | None = None
    conditional_ui: list[ScannedConditionalUI] = Field(default_factory=list)
    server_redirects: list[dict[str, Any]] = Field(default_factory=list)
    auth_guards: list[dict[str, Any]] = Field(default_factory=list)
    blocked_routes: list[str] = Field(default_factory=list)
    accessible_routes: list[str] = Field(default_factory=list)
    key_selectors: dict[str, Any] = Field(default_factory=dict)
    error_messages: dict[str, list[str]] = Field(default_factory=dict)
    success_indicators: dict[str, str] = Field(default_factory=dict)

    # Selector stability tracking
    prop_passthrough_issues: list[dict[str, Any]] = Field(default_factory=list)
    form_validations: list[dict[str, Any]] = Field(default_factory=list)
    post_login_flow: list[dict[str, Any]] = Field(default_factory=list)
    state_dependent_ui: list[dict[str, Any]] = Field(default_factory=list)
    css_module_selectors: list[dict[str, Any]] = Field(default_factory=list)
    stable_selectors: list[dict[str, Any]] = Field(default_factory=list)
