import argparse
import json
import logging
import os
import subprocess
import sys
import time
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from urllib.parse import urlparse
from urllib.request import urlopen


BASE_DIR = Path(__file__).resolve().parent.parent


@dataclass
class PageResult:
    endpoint: str
    url: str
    curl_status: Optional[int]
    curl_time: Optional[float]
    curl_size: Optional[int]
    selenium_time: Optional[float]
    selenium_size: Optional[int]
    missing_elements: List[str]
    has_internal_error: bool


def _setup_logger(log_path: Path) -> logging.Logger:
    logger = logging.getLogger("ui_check")
    logger.setLevel(logging.INFO)
    formatter = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")

    file_handler = logging.FileHandler(log_path, encoding="utf-8")
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

    stream_handler = logging.StreamHandler()
    stream_handler.setFormatter(formatter)
    logger.addHandler(stream_handler)
    return logger


def _load_json(path: Path) -> Dict:
    if not path.exists():
        return {}
    try:
        content = path.read_text(encoding="utf-8")
        data = json.loads(content) if content else {}
        return data if isinstance(data, dict) else {}
    except Exception:
        return {}


def _resolve_base_url(args_base: Optional[str]) -> str:
    if args_base:
        return args_base.rstrip("/")

    env_base = os.getenv("GNOMENALL_BASE_URL") or os.getenv("GNM_BASE_URL")
    if env_base:
        return env_base.rstrip("/")

    env_host = os.getenv("GNOMENALL_HOST") or os.getenv("GNM_HOST")
    env_port = os.getenv("GNOMENALL_PORT") or os.getenv("GNM_PORT")

    settings = _load_json(BASE_DIR / "config" / "settings.json")
    config = _load_json(BASE_DIR / "config.json")

    host = (
        env_host
        or settings.get("server_host")
        or config.get("server_host")
    )
    port = (
        env_port
        or settings.get("server_port")
        or config.get("server_port")
    )
    scheme = (
        settings.get("server_scheme")
        or config.get("server_scheme")
        or "http"
    )

    if host and port:
        return f"{scheme}://{host}:{port}"

    raise RuntimeError(
        "Липсва base URL. Задайте GNOMENALL_BASE_URL или GNOMENALL_HOST/GNOMENALL_PORT, "
        "или добавете server_host/server_port в config/settings.json."
    )


def _curl_status_check(
    url: str, logger: logging.Logger
) -> Tuple[Optional[int], Optional[float], Optional[int]]:
    try:
        result = subprocess.run(
            [
                "curl",
                "-s",
                "-o",
                "NUL",
                "-w",
                "%{http_code} %{time_total} %{size_download}",
                url,
            ],
            capture_output=True,
            text=True,
            check=False,
        )
        output = (result.stdout or "").strip().split()
        if len(output) != 3:
            logger.warning("Curl не върна пълни данни за %s", url)
            return None, None, None
        status = int(output[0]) if output[0].isdigit() else None
        time_total = float(output[1]) if output[1] else None
        size = int(float(output[2])) if output[2] else None
        return status, time_total, size
    except FileNotFoundError:
        logger.error("curl не е наличен в системата")
        return None, None, None


def _fetch_html(url: str) -> str:
    with urlopen(url, timeout=10) as response:
        return response.read().decode("utf-8", errors="replace")


def _expected_elements(endpoint: str) -> List[Tuple[str, str]]:
    base = [("heading", "h1_or_h2"), ("data-i18n", "[data-i18n]")]
    if endpoint == "/":
        return base + [("catalog", "#catalog-list")]
    if endpoint == "/vault":
        return base + [("vault", "#vault-status"), ("actions", "#vault-unlock")]
    if endpoint == "/senate":
        return base + [("form", "#add-senator-form")]
    if endpoint == "/forge":
        return base + [("form", "#forge-form")]
    return base


def _check_html_elements(
    url: str, endpoint: str, logger: logging.Logger
) -> Tuple[List[str], bool]:
    try:
        html = _fetch_html(url)
    except Exception as exc:
        logger.error("Грешка при HTML fetch за %s: %s", url, exc)
        return ["html_fetch"], True

    has_internal_error = "Internal Server Error" in html

    try:
        from bs4 import BeautifulSoup
    except Exception:
        logger.warning("BeautifulSoup не е наличен, пропускане на HTML проверките")
        return [], has_internal_error

    soup = BeautifulSoup(html, "html.parser")
    missing: List[str] = []

    for label, selector in _expected_elements(endpoint):
        if selector == "h1_or_h2":
            found = soup.find("h1") or soup.find("h2")
        else:
            found = soup.select_one(selector)
        if not found:
            missing.append(label)

    if has_internal_error:
        logger.error("Открит 'Internal Server Error' в %s", url)
    if missing:
        logger.warning("Липсващи елементи за %s: %s", url, ", ".join(missing))

    return missing, has_internal_error


def _take_screenshot(
    url: str, output_path: Path, logger: logging.Logger
) -> Tuple[Optional[float], Optional[int]]:
    try:
        from selenium import webdriver
        from selenium.webdriver.firefox.options import Options
        from selenium.webdriver.firefox.service import Service
    except Exception:
        logger.warning("Selenium не е наличен, пропускане на screenshot за %s", url)
        return None, None

    options = Options()
    options.add_argument("-headless")

    start = time.perf_counter()
    driver = None
    try:
        try:
            from webdriver_manager.firefox import GeckoDriverManager

            service = Service(GeckoDriverManager().install())
            driver = webdriver.Firefox(options=options, service=service)
        except Exception:
            driver = webdriver.Firefox(options=options)

        driver.set_window_size(1920, 1080)
        driver.get(url)
        page_source = driver.page_source or ""
        output_path.parent.mkdir(parents=True, exist_ok=True)
        driver.save_screenshot(str(output_path))
        duration = time.perf_counter() - start
        logger.info("Screenshot записан: %s", output_path)
        return duration, len(page_source.encode("utf-8"))
    except Exception as exc:
        logger.error("Грешка при screenshot за %s: %s", url, exc)
        return None, None
    finally:
        if driver:
            driver.quit()


def _parse_port(base_url: str) -> Optional[int]:
    parsed = urlparse(base_url)
    if parsed.port:
        return int(parsed.port)
    return None


def _is_server_up(base_url: str) -> bool:
    try:
        with urlopen(f"{base_url}/", timeout=2) as response:
            return response.status in (200, 302, 303, 307, 308)
    except Exception:
        return False


def _start_server(
    base_url: str, logger: logging.Logger, output_dir: Path
) -> Optional[subprocess.Popen]:
    if _is_server_up(base_url):
        return None

    port = _parse_port(base_url)
    if port is None:
        logger.error("Не може да се стартира сървър без порт в base URL")
        return None

    log_path = output_dir / "server.log"
    logger.info("Сървърът не работи. Стартиране на Gnomenall...")
    with open(log_path, "w", encoding="utf-8") as handle:
        process = subprocess.Popen(
            [
                sys.executable,
                "main.py",
                "--server",
                "quart",
                "--port",
                str(port),
            ],
            cwd=str(BASE_DIR),
            stdout=handle,
            stderr=subprocess.STDOUT,
        )

    for _ in range(30):
        if _is_server_up(base_url):
            logger.info("Сървърът е активен.")
            return process
        time.sleep(1)

    logger.error("Сървърът не стартира навреме.")
    return process


def main() -> int:
    parser = argparse.ArgumentParser(description="UI check за Gnomenall 312")
    parser.add_argument("--base-url", default=None, help="Base URL на локалния сървър")
    parser.add_argument(
        "--output",
        default="assets/screenshots/ui_check",
        help="Директория за screenshots",
    )
    args = parser.parse_args()

    output_dir = BASE_DIR / args.output
    output_dir.mkdir(parents=True, exist_ok=True)
    log_path = output_dir / "ui_check.log"
    logger = _setup_logger(log_path)

    try:
        base_url = _resolve_base_url(args.base_url)
    except RuntimeError as exc:
        logger.error(str(exc))
        return 2

    logger.info("Старт UI check за %s", base_url)

    server_proc = _start_server(base_url, logger, output_dir)

    endpoints = ["/", "/senate", "/vault", "/forge"]
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    results: List[PageResult] = []

    for endpoint in endpoints:
        url = f"{base_url}{endpoint}"
        status, time_total, size = _curl_status_check(url, logger)
        logger.info(
            "curl статус %s -> %s | time=%s | size=%s",
            url,
            status,
            time_total,
            size,
        )

        missing, internal_error = _check_html_elements(url, endpoint, logger)

        screenshot_name = (
            f"{endpoint.strip('/').replace('/', '_') or 'home'}_{timestamp}.png"
        )
        screenshot_path = output_dir / screenshot_name
        selenium_time, selenium_size = _take_screenshot(url, screenshot_path, logger)

        results.append(
            PageResult(
                endpoint=endpoint,
                url=url,
                curl_status=status,
                curl_time=time_total,
                curl_size=size,
                selenium_time=selenium_time,
                selenium_size=selenium_size,
                missing_elements=missing,
                has_internal_error=internal_error,
            )
        )

    ok = True
    for result in results:
        if result.curl_status not in (200, 302, 303, 307, 308):
            ok = False
        if result.has_internal_error or result.missing_elements:
            ok = False

    summary_path = output_dir / "ui_check_summary.json"
    summary_payload = [result.__dict__ for result in results]
    summary_path.write_text(
        json.dumps(summary_payload, indent=2, ensure_ascii=False), encoding="utf-8"
    )

    logger.info("UI check завършен: %s", "OK" if ok else "NOK")
    logger.info("Screenshot-и: %s", output_dir)

    if server_proc:
        server_proc.terminate()
        try:
            server_proc.wait(timeout=5)
        except Exception:
            server_proc.kill()

    return 0 if ok else 1


if __name__ == "__main__":
    raise SystemExit(main())
