from __future__ import annotations

import json
import sys
import time
from typing import Any

from playwright.sync_api import Browser, BrowserContext, Error, Page, Response, TimeoutError, sync_playwright


BASE_URL = "https://mod-cms-onboarding.dry-salad-ffa1.workers.dev"
INSTALL_URL = f"{BASE_URL}/install"
LOGIN_URL = f"{BASE_URL}/admin/login"
ADMIN_EMAIL = "owner-final@example.com"
ADMIN_PASSWORD = "Fin4l!VerifyPass#2026"
SITE_TITLE = "Final Verify Site"
TAGLINE = "Verification run"


class RunFailure(Exception):
    pass


def safe_text(response: Response) -> str:
    try:
        return response.text()
    except Exception as exc:  # pragma: no cover - diagnostic path
        return f"<failed to read body: {exc}>"


def selector_visible(page: Page, selector: str) -> bool:
    locator = page.locator(selector)
    try:
        return locator.is_visible()
    except Error:
        return False


def maybe_fill(page: Page, selector: str, value: str) -> None:
    locator = page.locator(selector)
    try:
        if locator.is_visible():
            locator.fill(value)
    except Error:
        return


def poll_leave_url(page: Page, forbidden_suffix: str, timeout_seconds: float) -> str:
    deadline = time.time() + timeout_seconds
    last_url = page.url
    while time.time() < deadline:
        last_url = page.url
        if not last_url.endswith(forbidden_suffix):
            return last_url
        page.wait_for_timeout(250)
    raise RunFailure(f"URL did not leave {forbidden_suffix} within {timeout_seconds:.0f}s; last URL: {last_url}")


def describe_dashboard(page: Page) -> str:
    title = page.title().strip() or "<no title>"
    heading = ""
    try:
        heading = page.locator("h1").first.inner_text(timeout=2000).strip()
    except Exception:
        heading = ""
    if heading:
        return f"title={title}; h1={heading}; url={page.url}"
    return f"title={title}; url={page.url}"


def attach_listeners(page: Page, console_entries: list[str], console_problems: list[str], failed_requests: list[dict[str, Any]], install_responses: list[dict[str, Any]]) -> None:
    def on_console(msg: Any) -> None:
        text = msg.text
        entry = f"[console:{msg.type}] {text}"
        if msg.type in {"log", "warning", "error"}:
            console_entries.append(entry)
        if msg.type in {"warning", "error"}:
            console_problems.append(entry)

    def on_pageerror(exc: Any) -> None:
        text = str(exc)
        console_entries.append(f"[pageerror] {text}")
        console_problems.append(f"[pageerror] {text}")

    def on_response(response: Response) -> None:
        url = response.url
        body = safe_text(response)
        record = {
            "method": response.request.method,
            "url": url,
            "status": response.status,
            "body": body,
        }
        if "/api/install" in url:
            headers = response.headers
            record["location"] = headers.get("location", "")
            install_responses.append(record)
        if response.status >= 400:
            failed_requests.append(record)

    page.on("console", on_console)
    page.on("pageerror", on_pageerror)
    page.on("response", on_response)


def choose_visible_button(page: Page) -> tuple[str, Any]:
    continue_button = page.get_by_role("button", name="Continue")
    finish_button = page.get_by_role("button", name="Finish setup")

    finish_visible = finish_button.is_visible()
    continue_visible = continue_button.is_visible()

    if finish_visible:
        return "Finish setup", finish_button
    if continue_visible:
        return "Continue", continue_button
    raise RunFailure(f"No visible primary button found on {page.url}")


def run_onboarding(page: Page) -> tuple[str, list[dict[str, Any]]]:
    page.goto(INSTALL_URL, wait_until="domcontentloaded")
    page.wait_for_load_state("networkidle")

    clicked_labels: list[str] = []

    for attempt in range(1, 9):
        maybe_fill(page, 'input[name="siteTitle"]', SITE_TITLE)
        maybe_fill(page, 'input[name="tagline"]', TAGLINE)
        maybe_fill(page, 'input[name="adminEmail"]', ADMIN_EMAIL)
        maybe_fill(page, 'input[name="adminPassword"]', ADMIN_PASSWORD)

        label, button = choose_visible_button(page)
        if not button.is_enabled():
            raise RunFailure(f"Visible button '{label}' is disabled on loop iteration {attempt}")

        clicked_labels.append(label)
        button.click()

        if label == "Finish setup":
            final_url = poll_leave_url(page, "/install", 15)
            return final_url, [{"iteration": attempt, "clicked": label, "url": final_url}]

        page.wait_for_timeout(300)

    raise RunFailure(f"Loop exhausted after 8 iterations without clicking Finish setup; clicked={clicked_labels}")


def login_with_mouse(browser: Browser, console_entries: list[str], console_problems: list[str], failed_requests: list[dict[str, Any]]) -> tuple[BrowserContext, Page, str]:
    fresh_context = browser.new_context()
    login_page = fresh_context.new_page()
    attach_listeners(login_page, console_entries, console_problems, failed_requests, [])
    login_page.goto(LOGIN_URL, wait_until="domcontentloaded")
    login_page.wait_for_load_state("networkidle")

    email_locator = login_page.locator('input[name="email"], input[type="email"]').first
    password_locator = login_page.locator('input[name="password"], input[type="password"]').first
    if not email_locator.is_visible():
        raise RunFailure("Login email field is not visible on /admin/login")
    if not password_locator.is_visible():
        raise RunFailure("Login password field is not visible on /admin/login")

    email_locator.fill(ADMIN_EMAIL)
    password_locator.fill(ADMIN_PASSWORD)

    sign_in_button = login_page.get_by_role("button", name="Sign in")
    if not sign_in_button.is_visible():
        raise RunFailure("Sign in button is not visible on /admin/login")
    sign_in_button.click()

    final_url = poll_leave_url(login_page, "/admin/login", 15)
    if "/admin/login" in final_url:
        raise RunFailure(f"Mouse-click login stayed on login page; last URL: {final_url}")
    if "/admin" not in final_url:
        raise RunFailure(f"Mouse-click login left /admin/login but did not land on admin; final URL: {final_url}")

    body_text = login_page.locator("body").inner_text(timeout=5000)
    if "error" in body_text.lower() and "sign in" not in body_text.lower():
        raise RunFailure(f"Mouse-click login landed on an error-like page; final URL: {final_url}")
    return fresh_context, login_page, describe_dashboard(login_page)


def print_report(onboarding_pass: bool, login_pass: bool, install_record: dict[str, Any] | None, console_problems: list[str], failed_requests: list[dict[str, Any]], dashboard_description: str) -> None:
    if install_record is None:
        install_summary = "none captured"
    else:
        location = install_record.get("location", "")
        install_summary = f"status={install_record['status']} location={location or '<none>'} body={install_record['body']!r}"

    print(f"onboarding: {'PASS' if onboarding_pass else 'FAIL'} | {install_summary}")
    print(f"mouse-click-login: {'PASS' if login_pass else 'FAIL'}")
    print("console-errors-warnings:")
    if console_problems:
        for entry in console_problems:
            print(entry)
    else:
        print("none")
    print("failed-requests:")
    if failed_requests:
        for item in failed_requests:
            print(f"[{item['method']}] {item['url']} -> {item['status']} body={item['body']!r}")
    else:
        print("none")
    print(f"dashboard: {dashboard_description}")


def main() -> int:
    console_entries: list[str] = []
    console_problems: list[str] = []
    failed_requests: list[dict[str, Any]] = []
    install_responses: list[dict[str, Any]] = []
    onboarding_pass = False
    login_pass = False
    dashboard_description = "not reached"
    install_record: dict[str, Any] | None = None

    try:
        with sync_playwright() as playwright:
            browser: Browser = playwright.chromium.launch(headless=True, args=["--no-sandbox"])
            context = browser.new_context()
            page = context.new_page()
            attach_listeners(page, console_entries, console_problems, failed_requests, install_responses)

            final_url, _ = run_onboarding(page)
            if not install_responses:
                raise RunFailure("No /api/install response was captured")
            install_record = install_responses[-1]

            status = int(install_record["status"])
            location = str(install_record.get("location", ""))
            if status not in {200, 303}:
                raise RunFailure(f"/api/install returned unexpected status {status} with body {install_record['body']!r}")
            if status == 303 and location not in {"/admin", "/admin/login"}:
                raise RunFailure(
                    f"/api/install returned 303 to unexpected location {location or '<none>'} with body {install_record['body']!r}"
                )
            if not final_url.startswith(f"{BASE_URL}/admin"):
                raise RunFailure(f"Finish setup left /install but did not reach admin area; final URL: {final_url}")

            onboarding_pass = True

            fresh_context, login_page, dashboard_description = login_with_mouse(browser, console_entries, console_problems, failed_requests)
            login_pass = True
            dashboard_description = describe_dashboard(login_page)
            fresh_context.close()
            browser.close()
    except (RunFailure, TimeoutError) as exc:
        if install_responses:
            install_record = install_responses[-1]
        print_report(onboarding_pass, login_pass, install_record, console_problems, failed_requests, dashboard_description)
        print(f"failure-step: {exc}")
        return 1
    except Exception as exc:  # pragma: no cover - final adversarial catch
        if install_responses:
            install_record = install_responses[-1]
        print_report(onboarding_pass, login_pass, install_record, console_problems, failed_requests, dashboard_description)
        print(f"failure-step: unexpected error: {exc}")
        return 1

    print_report(onboarding_pass, login_pass, install_record, console_problems, failed_requests, dashboard_description)
    return 0


if __name__ == "__main__":
    sys.exit(main())
