from __future__ import annotations

import argparse
import json
import os
import re
import time
import urllib.error
import urllib.parse
import urllib.request
from html.parser import HTMLParser
from pathlib import Path
from typing import Any

from fetch_1c_its_docs import charset_from_content_type, request_safe_url
from one_c_its_platform import materialize_doc_url


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUTPUT = ROOT / "reports" / "1c-its-access-check.json"
DEFAULT_TEST_URL = "https://its.1c.ru/db/v8316doc#bookmark:dev:TI000000044"


class AccessHtmlParser(HTMLParser):
    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.title_parts: list[str] = []
        self._in_title = False
        self.login_links = 0
        self.user_profile_markers = 0
        self.paywall_markers = 0
        self.data_access_false = 0
        self.iframe_srcs: list[str] = []

    def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
        tag = tag.lower()
        values = {name.lower(): value or "" for name, value in attrs}
        if tag == "title":
            self._in_title = True
        href = values.get("href", "")
        class_name = values.get("class", "")
        if "/user/auth" in href:
            self.login_links += 1
        if "paywall" in class_name:
            self.paywall_markers += 1
        if values.get("data-access") == "false":
            self.data_access_false += 1
        if tag == "iframe" and values.get("src"):
            self.iframe_srcs.append(values["src"])

    def handle_endtag(self, tag: str) -> None:
        if tag.lower() == "title":
            self._in_title = False

    def handle_data(self, data: str) -> None:
        if self._in_title:
            self.title_parts.append(data.strip())
        if "Общий профиль" in data or "Доступ до" in data:
            self.user_profile_markers += 1

    @property
    def title(self) -> str:
        return " ".join(part for part in self.title_parts if part)


def read_cookie(cookie_file: Path | None) -> str:
    if cookie_file:
        return cookie_file.read_text(encoding="utf-8").strip()
    return os.environ.get("ONEC_ITS_COOKIE", "").strip()


def fetch_text(url: str, *, cookie: str, timeout: int, referer: str | None = None) -> tuple[int, str, str]:
    headers = {"User-Agent": "Codex 1C ITS access check"}
    if cookie:
        headers["Cookie"] = cookie
    if referer:
        headers["Referer"] = referer
        headers["X-Referer"] = referer
    request = urllib.request.Request(request_safe_url(url), headers=headers)
    with urllib.request.urlopen(request, timeout=timeout) as response:
        body = response.read()
        content_type = response.headers.get("Content-Type", "")
        charset = charset_from_content_type(content_type)
        return int(response.status), content_type, body.decode(charset, errors="replace")


def first_src_url(page_url: str, iframe_srcs: list[str]) -> str | None:
    for src in iframe_srcs:
        if "/db/content/" in src and "/src/" in src:
            return urllib.parse.urljoin(page_url, src)
    return None


def check_access(url: str, *, cookie: str, timeout: int) -> dict[str, Any]:
    materialized = materialize_doc_url(url)
    result: dict[str, Any] = {
        "schema": "onec_its_access_check.v1",
        "checked_at_unix": int(time.time()),
        "target_url": url,
        "materialized_url": materialized,
        "cookie_present": bool(cookie.strip()),
        "page": {},
        "src": {},
        "status": "unknown",
        "findings": [],
    }
    if not cookie.strip():
        result["status"] = "failed"
        result["findings"].append("cookie_missing")
        return result

    try:
        status, content_type, text = fetch_text(materialized, cookie=cookie, timeout=timeout)
        parser = AccessHtmlParser()
        parser.feed(text)
        src_url = first_src_url(materialized, parser.iframe_srcs)
        result["page"] = {
            "http_status": status,
            "content_type": content_type,
            "title": parser.title,
            "login_links": parser.login_links,
            "user_profile_markers": parser.user_profile_markers,
            "paywall_markers": parser.paywall_markers,
            "data_access_false": parser.data_access_false,
            "iframe_src": src_url,
        }
        if parser.login_links:
            result["findings"].append("login_links_present")
        if parser.paywall_markers:
            result["findings"].append("paywall_marker_present")
        if parser.data_access_false:
            result["findings"].append("data_access_false")
    except Exception as exc:  # noqa: BLE001
        result["page"] = {"error": f"{type(exc).__name__}: {exc}"}
        result["status"] = "failed"
        result["findings"].append("page_fetch_failed")
        return result

    src_url = result["page"].get("iframe_src")
    if src_url:
        try:
            src_status, src_content_type, src_text = fetch_text(str(src_url), cookie=cookie, timeout=timeout, referer=materialized)
            visible_words = len(re.findall(r"[A-Za-zА-Яа-яЁё0-9_]+", src_text))
            result["src"] = {
                "http_status": src_status,
                "content_type": src_content_type,
                "chars": len(src_text),
                "word_count": visible_words,
            }
            if visible_words < 50:
                result["findings"].append("src_low_text")
        except urllib.error.HTTPError as exc:
            result["src"] = {"http_status": exc.code, "error": str(exc), "url": src_url}
            result["findings"].append(f"src_http_{exc.code}")
        except Exception as exc:  # noqa: BLE001
            result["src"] = {"error": f"{type(exc).__name__}: {exc}", "url": src_url}
            result["findings"].append("src_fetch_failed")
    else:
        result["findings"].append("src_iframe_missing")

    blocking = {"login_links_present", "paywall_marker_present", "data_access_false", "src_http_401", "src_fetch_failed"}
    result["status"] = "failed" if any(item in blocking for item in result["findings"]) else "ok"
    return result


def main() -> int:
    parser = argparse.ArgumentParser(description="Check whether the stored 1C:ITS cookie can access protected documentation bodies.")
    parser.add_argument("--url", default=DEFAULT_TEST_URL)
    parser.add_argument("--cookie-file", type=Path)
    parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
    parser.add_argument("--timeout", type=int, default=30)
    parser.add_argument("--print", action="store_true", dest="print_report")
    args = parser.parse_args()

    report = check_access(args.url, cookie=read_cookie(args.cookie_file), timeout=args.timeout)
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps(report if args.print_report else {"status": report["status"], "findings": report["findings"], "output": str(args.output)}, ensure_ascii=False, indent=2))
    return 0 if report["status"] == "ok" else 1


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