#!/usr/bin/env python3
"""Validate the local setup for jpki-web."""

from __future__ import annotations

import importlib.util
import json
import re
import sys
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]

PROJECT_FILES = [
    "app/main.py",
    "app/static/index.html",
    "app/static/app.js",
    "app/static/styles.css",
    "app/jpki_signer/service.py",
    "app/jpki_signer/jpki/session.py",
    "app/jpki_signer/container/reader.py",
    "app/jpki_signer/container/writer.py",
    "requirements.txt",
    "README.md",
]

PYTHON_MODULES = {
    "fastapi": "fastapi",
    "uvicorn": "uvicorn[standard]",
    "multipart": "python-multipart",
    "cryptography": "cryptography",
    "asn1crypto": "asn1crypto",
    "smartcard": "pyscard",
}

README_REQUIRED_SNIPPETS = [
    "--host 127.0.0.1",
    "VPS / クラウド / 共有サーバー等にデプロイしては絶対にいけません",
    ".jpkiimg",
    "/api/read-certificate",
]

ROUTE_PATTERNS = {
    "health": r'@app\.get\("/api/health"\)',
    "verify": r'@app\.post\("/api/verify"\)',
    "sign": r'@app\.post\("/api/sign"\)',
    "read_certificate": r'@app\.post\("/api/read-certificate"\)',
}


def _ok(message: str) -> None:
    print(f"OK   {message}")


def _warn(message: str) -> None:
    print(f"WARN {message}")


def _ng(message: str) -> None:
    print(f"NG   {message}")


def _module_available(module_name: str) -> bool:
    return importlib.util.find_spec(module_name) is not None


def check_project_files() -> list[str]:
    errors: list[str] = []
    for relative in PROJECT_FILES:
        if (ROOT / relative).exists():
            _ok(f"found {relative}")
        else:
            errors.append(f"missing required file: {relative}")
            _ng(errors[-1])
    return errors


def check_python_modules() -> list[str]:
    errors: list[str] = []
    for module_name, package_name in PYTHON_MODULES.items():
        if _module_available(module_name):
            _ok(f"python package available: {package_name}")
        else:
            errors.append(
                f"missing Python package: {package_name} "
                f"(install with: python -m pip install -r requirements.txt)"
            )
            _ng(errors[-1])
    return errors


def check_main_routes(source: str) -> list[str]:
    errors: list[str] = []
    for route_name, pattern in ROUTE_PATTERNS.items():
        if re.search(pattern, source):
            _ok(f"FastAPI route found: {route_name}")
        else:
            errors.append(f"missing FastAPI route: {route_name}")
            _ng(errors[-1])

    if "allow_origins" in source and "127.0.0.1:8000" in source:
        _ok("CORS localhost allowlist found")
    else:
        errors.append("CORS localhost allowlist was not found")
        _ng(errors[-1])
    return errors


def check_readme_security(readme_text: str) -> list[str]:
    errors: list[str] = []
    for snippet in README_REQUIRED_SNIPPETS:
        if snippet in readme_text:
            _ok(f"README snippet found: {snippet}")
        else:
            errors.append(f"README required snippet missing: {snippet}")
            _ng(errors[-1])
    return errors


def check_openapi_import_safety() -> list[str]:
    """Import FastAPI app only when required packages exist, then inspect routes."""
    if not all(_module_available(name) for name in ("fastapi", "multipart")):
        _warn("skipping app import because FastAPI/python-multipart is unavailable")
        return []

    sys.path.insert(0, str(ROOT))
    try:
        from app.main import app  # type: ignore
    except Exception as exc:  # noqa: BLE001
        message = f"failed to import app.main: {type(exc).__name__}: {exc}"
        _ng(message)
        return [message]
    finally:
        try:
            sys.path.remove(str(ROOT))
        except ValueError:
            pass

    routes = sorted(route.path for route in app.routes)
    expected = ["/api/health", "/api/verify", "/api/sign", "/api/read-certificate"]
    errors: list[str] = []
    for path in expected:
        if path in routes:
            _ok(f"OpenAPI route registered: {path}")
        else:
            errors.append(f"OpenAPI route not registered: {path}")
            _ng(errors[-1])

    try:
        json.dumps(app.openapi())
        _ok("OpenAPI schema generated")
    except Exception as exc:  # noqa: BLE001
        errors.append(f"OpenAPI schema generation failed: {type(exc).__name__}: {exc}")
        _ng(errors[-1])
    return errors


def main() -> int:
    print(f"Checking jpki-web setup under: {ROOT}")
    errors: list[str] = []
    errors.extend(check_project_files())
    errors.extend(check_python_modules())

    main_py = ROOT / "app" / "main.py"
    readme = ROOT / "README.md"
    if main_py.exists():
        errors.extend(check_main_routes(main_py.read_text(encoding="utf-8")))
    if readme.exists():
        errors.extend(check_readme_security(readme.read_text(encoding="utf-8")))
    errors.extend(check_openapi_import_safety())

    if errors:
        print()
        print("Setup check failed:")
        for error in errors:
            print(f"- {error}")
        return 1

    print()
    print("JPKI Web setup check OK.")
    return 0


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