"""Assertion engine — validate expectations after flow steps.

Each assertion type is a function that receives the Playwright page (and
optional extra context) and returns an AssertionResult.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Any

from playwright.sync_api import Page

log = logging.getLogger("antiqa.flows")


@dataclass
class AssertionResult:
    type: str
    passed: bool
    expected: str
    actual: str
    message: str = ""


def check_url_contains(page: Page, value: str, **_: Any) -> AssertionResult:
    actual = page.url
    passed = value in actual
    return AssertionResult(
        type="url_contains",
        passed=passed,
        expected=value,
        actual=actual,
        message="" if passed else f"URL '{actual}' does not contain '{value}'",
    )


def check_url_equals(page: Page, value: str, **_: Any) -> AssertionResult:
    actual = page.url
    passed = actual.rstrip("/") == value.rstrip("/")
    return AssertionResult(
        type="url_equals",
        passed=passed,
        expected=value,
        actual=actual,
        message="" if passed else f"URL '{actual}' != '{value}'",
    )


def check_element_visible(page: Page, selector: str, **_: Any) -> AssertionResult:
    try:
        el = page.wait_for_selector(selector, state="visible", timeout=5000)
        passed = el is not None
    except Exception:
        passed = False
    return AssertionResult(
        type="element_visible",
        passed=passed,
        expected=f"'{selector}' is visible",
        actual="visible" if passed else "not found / hidden",
    )


def check_element_absent(page: Page, selector: str, **_: Any) -> AssertionResult:
    try:
        el = page.query_selector(selector)
        passed = el is None or not el.is_visible()
    except Exception:
        passed = True
    return AssertionResult(
        type="element_absent",
        passed=passed,
        expected=f"'{selector}' is absent",
        actual="absent" if passed else "visible",
    )


def check_text_equals(page: Page, selector: str, value: str = "", **_: Any) -> AssertionResult:
    try:
        el = page.wait_for_selector(selector, timeout=5000)
        actual = (el.text_content() or "").strip() if el else ""
    except Exception:
        actual = ""
    passed = actual == value
    return AssertionResult(
        type="text_equals",
        passed=passed,
        expected=value,
        actual=actual,
        message="" if passed else f"Text of '{selector}': '{actual}' != '{value}'",
    )


def check_text_contains(page: Page, selector: str, value: str = "", **_: Any) -> AssertionResult:
    try:
        el = page.wait_for_selector(selector, timeout=5000)
        actual = (el.text_content() or "").strip() if el else ""
    except Exception:
        actual = ""
    passed = value in actual
    return AssertionResult(
        type="text_contains",
        passed=passed,
        expected=value,
        actual=actual,
        message="" if passed else f"Text of '{selector}': '{actual}' does not contain '{value}'",
    )


ASSERTION_MAP: dict[str, Any] = {
    "url_contains": check_url_contains,
    "url_equals": check_url_equals,
    "element_visible": check_element_visible,
    "element_absent": check_element_absent,
    "text_equals": check_text_equals,
    "text_contains": check_text_contains,
}


def run_assertion(page: Page, assertion_def: dict[str, Any]) -> AssertionResult:
    """Dispatch a single assertion definition to the right checker."""
    atype = assertion_def.get("type", "")
    checker = ASSERTION_MAP.get(atype)
    if not checker:
        return AssertionResult(
            type=atype,
            passed=False,
            expected="",
            actual="",
            message=f"Unknown assertion type: '{atype}'",
        )
    return checker(page, **{k: v for k, v in assertion_def.items() if k != "type"})
