"""
HealingPage — the crown jewel of playwright-healer.

Wraps a Playwright Page so that EVERY locator call is automatically
self-healing.  Zero code changes needed: just swap `page` → `HealingPage(page)`.

Advantages over autoheal-locator:
  ✓ No separate adapter class — wraps Page directly
  ✓ Async context manager support
  ✓ Shadow DOM and iframe penetration built-in
  ✓ All Playwright Page navigation methods forwarded
  ✓ HealingLocator objects returned from every locator call
  ✓ Session report generated on context manager exit

Quick usage:
  from playwright_healer import HealingPage, auto_config

  async with HealingPage(page, auto_config()) as hp:
      await hp.goto("https://example.com")
      await hp.click("#submit-btn", "Submit button")   # heals automatically
      value = await hp.fill("#email", "Email field", "user@example.com")
      count = await hp.count(".product-card", "Product cards")
"""

from __future__ import annotations

import logging
from typing import Any, List, Optional

from playwright.async_api import Locator, Page

from playwright_healer.config import HealerConfig
from playwright_healer.locator import HealingLocator
from playwright_healer.pipeline import ElementNotFoundError, HealingPipeline
from playwright_healer.reporting import SessionReport

logger = logging.getLogger("playwright_healer.page")


class HealingPage:
    """
    A drop-in replacement for Playwright's Page that self-heals broken locators.

    Can be used as:
      1. Context manager:  async with HealingPage(page, config) as hp: ...
      2. Direct instance:  hp = HealingPage(page, config); await hp.goto(...)
                           ...
                           await hp.shutdown()
    """

    def __init__(
        self,
        page: Page,
        config: HealerConfig,
        test_name: str = "",
    ) -> None:
        self._page = page
        self._config = config
        self._pipeline = HealingPipeline(page, config, test_name)

    # ------------------------------------------------------------------
    # Context manager
    # ------------------------------------------------------------------

    async def __aenter__(self) -> "HealingPage":
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.shutdown()

    # ------------------------------------------------------------------
    # Healing locator factory
    # ------------------------------------------------------------------

    def locator(self, selector: str, description: str = "") -> HealingLocator:
        """Return a HealingLocator for the given selector."""
        return HealingLocator(selector, description or selector, self._pipeline)

    # ------------------------------------------------------------------
    # High-level healing actions (convenience API)
    # These shortcut finding + acting in one call, for minimum verbosity.
    # ------------------------------------------------------------------

    async def click(self, selector: str, description: str = "", **kwargs: Any) -> None:
        """Find element and click it, healing if needed."""
        await (await self._pipeline.find(selector, description or selector)).click(**kwargs)

    async def fill(
        self, selector: str, description: str = "", value: str = "", **kwargs: Any
    ) -> None:
        """Find element and fill it, healing if needed."""
        await (await self._pipeline.find(selector, description or selector)).fill(value, **kwargs)

    async def type_text(
        self, selector: str, description: str = "", text: str = "", **kwargs: Any
    ) -> None:
        """Find element and type into it, healing if needed."""
        await (await self._pipeline.find(selector, description or selector)).type(text, **kwargs)

    async def select(
        self, selector: str, description: str = "", value: Any = None, **kwargs: Any
    ) -> Any:
        """Find a <select> and select an option."""
        return await (await self._pipeline.find(selector, description or selector)).select_option(
            value, **kwargs
        )

    async def check(self, selector: str, description: str = "", **kwargs: Any) -> None:
        await (await self._pipeline.find(selector, description or selector)).check(**kwargs)

    async def uncheck(self, selector: str, description: str = "", **kwargs: Any) -> None:
        await (await self._pipeline.find(selector, description or selector)).uncheck(**kwargs)

    async def hover(self, selector: str, description: str = "", **kwargs: Any) -> None:
        await (await self._pipeline.find(selector, description or selector)).hover(**kwargs)

    async def get_text(self, selector: str, description: str = "", **kwargs: Any) -> str:
        loc = await self._pipeline.find(selector, description or selector)
        return await loc.inner_text(**kwargs)

    async def get_attribute(
        self, selector: str, attribute: str, description: str = "", **kwargs: Any
    ) -> Optional[str]:
        loc = await self._pipeline.find(selector, description or selector)
        return await loc.get_attribute(attribute, **kwargs)

    async def is_visible(self, selector: str, description: str = "") -> bool:
        try:
            loc = await self._pipeline.find(selector, description or selector)
            return await loc.is_visible()
        except ElementNotFoundError:
            return False

    async def is_present(self, selector: str, description: str = "") -> bool:
        return await self._pipeline.is_present(selector, description or selector)

    async def count(self, selector: str, description: str = "") -> int:
        try:
            locs = await self._pipeline.find_all(selector, description or selector)
            return len(locs)
        except ElementNotFoundError:
            return 0

    async def find_all(
        self, selector: str, description: str = ""
    ) -> List[HealingLocator]:
        """Return HealingLocators for all matching elements."""
        locators = await self._pipeline.find_all(selector, description or selector)
        return [
            HealingLocator(selector, description or selector, self._pipeline, loc)
            for loc in locators
        ]

    # ------------------------------------------------------------------
    # Direct element access — returns HealingLocator (lazy resolve)
    # ------------------------------------------------------------------

    def get_by_role(self, role: str, name: str = "", description: str = "") -> HealingLocator:
        selector = f"{role}::{name}" if name else role
        return HealingLocator(
            selector,
            description or f"{role} {name}",
            self._pipeline,
            self._page.get_by_role(role, name=name) if name else self._page.get_by_role(role),  # type: ignore[arg-type]
        )

    def get_by_text(self, text: str, description: str = "") -> HealingLocator:
        return HealingLocator(
            text,
            description or text,
            self._pipeline,
            self._page.get_by_text(text),
        )

    def get_by_placeholder(self, value: str, description: str = "") -> HealingLocator:
        return HealingLocator(
            value,
            description or value,
            self._pipeline,
            self._page.get_by_placeholder(value),
        )

    def get_by_label(self, text: str, description: str = "") -> HealingLocator:
        return HealingLocator(
            text,
            description or text,
            self._pipeline,
            self._page.get_by_label(text),
        )

    def get_by_test_id(self, test_id: str, description: str = "") -> HealingLocator:
        return HealingLocator(
            test_id,
            description or test_id,
            self._pipeline,
            self._page.get_by_test_id(test_id),
        )

    # ------------------------------------------------------------------
    # Playwright Page navigation & settings — fully forwarded
    # ------------------------------------------------------------------

    async def goto(self, url: str, **kwargs: Any) -> Any:
        return await self._page.goto(url, **kwargs)

    async def reload(self, **kwargs: Any) -> Any:
        return await self._page.reload(**kwargs)

    async def go_back(self, **kwargs: Any) -> Any:
        return await self._page.go_back(**kwargs)

    async def go_forward(self, **kwargs: Any) -> Any:
        return await self._page.go_forward(**kwargs)

    async def wait_for_load_state(self, state: str = "load", **kwargs: Any) -> None:
        return await self._page.wait_for_load_state(state, **kwargs)

    async def wait_for_url(self, url: str, **kwargs: Any) -> None:
        return await self._page.wait_for_url(url, **kwargs)

    async def screenshot(self, **kwargs: Any) -> bytes:
        return await self._page.screenshot(**kwargs)

    async def title(self) -> str:
        return await self._page.title()

    async def evaluate(self, expression: str, *args: Any, **kwargs: Any) -> Any:
        return await self._page.evaluate(expression, *args, **kwargs)

    async def set_viewport_size(self, width: int, height: int) -> None:
        return await self._page.set_viewport_size({"width": width, "height": height})

    async def close(self) -> None:
        return await self._page.close()

    @property
    def url(self) -> str:
        return self._page.url

    @property
    def raw_page(self) -> Page:
        """Access the underlying Playwright Page directly."""
        return self._page

    @property
    def session_report(self) -> SessionReport:
        return self._pipeline.session_report

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def shutdown(self) -> None:
        """Flush cache, print summary, generate reports."""
        await self._pipeline.shutdown()
