from __future__ import annotations

import atexit
import os
from dataclasses import dataclass
from typing import Any

from playwright.sync_api import Browser, BrowserContext, Page, Playwright, sync_playwright

from e2e.utils.account_store import account_store


def dual_browser_enabled() -> bool:
    return os.getenv("E2E_DUAL_BROWSER", "1").strip().lower() not in {"0", "false", "no", "off"}


def dual_browser_headed() -> bool:
    return os.getenv("E2E_DUAL_BROWSER_HEADED", "1").strip().lower() not in {"0", "false", "no", "off"}


def dual_browser_slow_mo() -> int:
    return int(os.getenv("E2E_DUAL_BROWSER_SLOW_MO", "200"))


def keep_browser_open() -> bool:
    return os.getenv("E2E_KEEP_BROWSER_OPEN", "").strip().lower() in {"1", "true", "yes", "on"}


def role_for_phone(phone: str) -> str:
    wanzhanggui_phone = (
        os.getenv("E2E_DUAL_BROWSER_WANZHANGGUI_PHONE", "").strip()
        or os.getenv("E2E_ORDER_WANZHANGGUI_PHONE", "").strip()
        or os.getenv("E2E_WANZHANGGUI_TENANT_PHONE", "17335564065").strip()
    )
    if phone == wanzhanggui_phone:
        return "wanzhanggui"

    account = account_store().find_main(phone) or account_store().find(phone=phone)
    if account and account.parent_phone:
        role = account.member_role or account.contact_name or "sub"
        if "打手" in role or role.lower() == "player":
            return f"wanyouji_player_{phone}"
        if "客服" in role or role.lower() in {"customer_service", "cs"}:
            return f"wanyouji_customer_service_{phone}"
        return f"wanyouji_member_{phone}"
    if account and account.wanyouji_status == "opened":
        return f"wanyouji_{phone}"
    if account and account.parent_phone:
        role = account.member_role or account.contact_name or "sub"
        if "鎵撴墜" in role or "打手" in role or role.lower() == "player":
            return f"wanyouji_player_{phone}"
        if "瀹㈡湇" in role or "客服" in role or role.lower() in {"customer_service", "cs"}:
            return f"wanyouji_customer_service_{phone}"
        return f"wanyouji_member_{phone}"
    return f"tenant_{phone}"


def channel_for_role(role: str) -> str:
    return "chrome"


def assert_account_page(page: Page, phone: str) -> None:
    if not dual_browser_enabled():
        return
    expected_role = role_for_phone(phone)
    actual_role = getattr(page, "_e2e_dual_browser_role", "")
    assert actual_role == expected_role, (
        f"账号 {phone} 未路由到独立浏览器上下文；expected_role={expected_role}; "
        f"actual_role={actual_role}; url={page.url}"
    )


def assert_distinct_account_contexts(
    left_page: Page,
    left_phone: str,
    right_page: Page,
    right_phone: str,
) -> None:
    if not dual_browser_enabled():
        return
    assert_account_page(left_page, left_phone)
    assert_account_page(right_page, right_phone)
    assert left_page.context is not right_page.context, (
        f"账号 {left_phone} 与 {right_phone} 复用了同一个 BrowserContext，"
        "这会导致退出/登录互相污染。"
    )


@dataclass
class BrowserSlot:
    role: str
    channel: str
    browser: Browser
    context: BrowserContext
    page: Page
    current_phone: str = ""


class DualBrowserManager:
    def __init__(self) -> None:
        self._sync_context: Any | None = None
        self._playwright: Playwright | None = None
        self._browser: Browser | None = None
        self._channel: str = "chrome"
        self._slots: dict[str, BrowserSlot] = {}
        self._active_page: Page | None = None

    def active_page(self) -> Page | None:
        if self._active_page is not None and not self._active_page.is_closed():
            return self._active_page
        return None

    def set_active_page(self, page: Page) -> None:
        self._active_page = page

    def page_for_account(
        self,
        phone: str,
        *,
        viewport: dict[str, int] | None = None,
        seed_page: Page | None = None,
    ) -> Page:
        if not dual_browser_enabled():
            raise RuntimeError("Dual browser mode is disabled.")
        role = role_for_phone(phone)
        seed_slot = self._slot_from_seed_page(role, seed_page)
        if seed_slot is not None:
            self._slots[role] = seed_slot
            self.set_active_page(seed_slot.page)
            return seed_slot.page
        slot = self._slots.get(role)
        if slot is None:
            adopted_slot = self._slot_from_unmarked_seed_page(role, seed_page)
            if adopted_slot is not None:
                self._slots[role] = adopted_slot
                self.set_active_page(adopted_slot.page)
                return adopted_slot.page
            slot = self._launch_slot(role, viewport=viewport, seed_page=seed_page)
            self._slots[role] = slot
        self.set_active_page(slot.page)
        return slot.page

    def _slot_from_seed_page(self, role: str, seed_page: Page | None) -> BrowserSlot | None:
        if seed_page is None:
            return None
        seed_role = getattr(seed_page, "_e2e_dual_browser_role", "")
        if seed_role != role:
            return None
        browser = seed_page.context.browser
        if browser is None:
            return None
        return BrowserSlot(
            role=role,
            channel=str(getattr(seed_page, "_e2e_dual_browser_channel", channel_for_role(role))),
            browser=browser,
            context=seed_page.context,
            page=seed_page,
        )

    def _slot_from_unmarked_seed_page(self, role: str, seed_page: Page | None) -> BrowserSlot | None:
        if seed_page is None or self._browser is not None or self._slots:
            return None
        if os.getenv("PYTEST_CURRENT_TEST"):
            return None
        browser = seed_page.context.browser
        if browser is None or not browser.is_connected():
            return None
        channel = channel_for_role(role)
        self._browser = browser
        self._channel = channel
        setattr(seed_page, "_e2e_dual_browser_role", role)
        setattr(seed_page, "_e2e_dual_browser_channel", channel)
        return BrowserSlot(role=role, channel=channel, browser=browser, context=seed_page.context, page=seed_page)

    def remember_login(self, phone: str) -> None:
        role = role_for_phone(phone)
        slot = self._slots.get(role)
        if slot:
            slot.current_phone = phone
            setattr(slot.page, "_e2e_current_main_account", phone)
            self.set_active_page(slot.page)

    def register_page_for_account(self, phone: str, page: Page) -> None:
        role = role_for_phone(phone)
        self.register_page_for_role(role, page, current_phone=phone)

    def register_page_for_role(self, role: str, page: Page, current_phone: str = "") -> None:
        slot = self._slots.get(role)
        if not slot:
            return
        setattr(page, "_e2e_dual_browser_role", role)
        setattr(page, "_e2e_dual_browser_channel", slot.channel)
        if current_phone:
            setattr(page, "_e2e_current_main_account", current_phone)
        slot.page = page
        slot.context = page.context
        slot.browser = page.context.browser or slot.browser
        if current_phone:
            slot.current_phone = current_phone
        self.set_active_page(page)

    def _launch_slot(
        self,
        role: str,
        *,
        viewport: dict[str, int] | None = None,
        seed_page: Page | None = None,
    ) -> BrowserSlot:
        channel = channel_for_role(role)
        viewport = viewport or {
            "width": int(os.getenv("E2E_VIEWPORT_WIDTH", "1440")),
            "height": int(os.getenv("E2E_VIEWPORT_HEIGHT", "900")),
        }
        browser = self._ensure_browser(seed_page=seed_page, viewport=viewport, channel=channel)
        context = browser.new_context(viewport=viewport, screen=viewport)
        page = context.new_page()
        setattr(page, "_e2e_dual_browser_role", role)
        setattr(page, "_e2e_dual_browser_channel", channel)
        return BrowserSlot(role=role, channel=channel, browser=browser, context=context, page=page)

    def _ensure_browser(
        self,
        *,
        seed_page: Page | None,
        viewport: dict[str, int],
        channel: str,
    ) -> Browser:
        if self._browser is not None and self._browser.is_connected():
            return self._browser
        browser_type = self._browser_type(seed_page)
        self._channel = channel
        self._browser = browser_type.launch(
            channel=channel,
            headless=not dual_browser_headed(),
            slow_mo=dual_browser_slow_mo(),
            args=[f"--window-size={viewport['width']},{viewport['height']}"],
        )
        return self._browser

    def _browser_type(self, seed_page: Page | None):
        if seed_page is not None:
            browser = seed_page.context.browser
            if browser is not None:
                return browser.browser_type
        return self._ensure_playwright().chromium

    def _ensure_playwright(self) -> Playwright:
        if self._playwright is None:
            self._sync_context = sync_playwright()
            self._playwright = self._sync_context.start()
        return self._playwright

    def close(self) -> None:
        if keep_browser_open():
            return
        for slot in reversed(list(self._slots.values())):
            try:
                slot.context.close()
            except Exception:
                pass
        self._slots.clear()
        self._active_page = None
        if self._browser is not None:
            try:
                self._browser.close()
            except Exception:
                pass
            self._browser = None
        if self._sync_context is not None:
            try:
                self._sync_context.stop()
            except Exception:
                pass
            self._sync_context = None
            self._playwright = None


_manager = DualBrowserManager()
atexit.register(_manager.close)


def dual_browser_manager() -> DualBrowserManager:
    return _manager
