#!/usr/bin/env python3
"""Lightweight release checks for AssetManager.

The checks are intentionally read-only and do not require a running database.
A failure exits with status 1 so the script can be used in CI or a release job.
"""
from __future__ import annotations

import compileall
import importlib.util
import os
from pathlib import Path
import subprocess
import sys

ROOT = Path(__file__).resolve().parents[1]
APP_DIR = ROOT / "app"


def fail(message: str) -> None:
    print(f"[FAIL] {message}")
    raise SystemExit(1)


def ok(message: str) -> None:
    print(f"[ OK ] {message}")


def check_version() -> None:
    path = APP_DIR / "version.py"
    spec = importlib.util.spec_from_file_location("assetmanager_release_version", path)
    if spec is None or spec.loader is None:
        fail("app/version.py could not be loaded")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    app_version = getattr(module, "APP_VERSION", None)
    conventional = getattr(module, "__version__", None)
    if not isinstance(app_version, str) or not app_version.strip():
        fail("APP_VERSION is missing or empty")
    if conventional != app_version:
        fail("__version__ must be an alias of APP_VERSION")
    version_file = (ROOT / "VERSION").read_text(encoding="utf-8").strip()
    if version_file != app_version:
        fail(f"VERSION ({version_file}) and app/version.py ({app_version}) differ")
    readme = (ROOT / "README.md").read_text(encoding="utf-8")
    if app_version not in readme:
        fail(f"README.md does not contain current version {app_version}")
    ok(f"Version exports are valid ({app_version})")



def check_repository_documentation() -> None:
    required = [
        ROOT / "README.md",
        ROOT / "CONTRIBUTING.md",
        ROOT / "SECURITY.md",
        ROOT / "THIRD_PARTY_NOTICES.md",
        ROOT / "docs" / "version-history" / "README.md",
        ROOT / "docs" / "version-history" / f"UPDATE-{(ROOT / 'VERSION').read_text(encoding='utf-8').strip()}.md",
    ]
    missing = [str(path.relative_to(ROOT)) for path in required if not path.is_file()]
    if missing:
        fail("Required repository documentation is missing: " + ", ".join(missing))
    root_notes = sorted(ROOT.glob("UPDATE-*.txt")) + sorted(ROOT.glob("UPDATE-*.md"))
    if root_notes:
        fail("Release notes must be stored under docs/version-history/: " + ", ".join(path.name for path in root_notes))
    ok("Repository documentation layout is valid")

def check_compile() -> None:
    if not compileall.compile_dir(str(APP_DIR), quiet=1, force=True):
        fail("Python compilation failed")
    ok("Python sources compile")


def check_translations() -> None:
    script = ROOT / "tools" / "check_translations.py"
    result = subprocess.run([sys.executable, str(script)], cwd=ROOT, text=True)
    if result.returncode:
        fail("Translation check failed")
    ok("Translation check passed")


def check_templates() -> None:
    try:
        from jinja2 import Environment, FileSystemLoader
    except ImportError as exc:
        fail(f"Jinja2 is unavailable: {exc}")
    template_dir = APP_DIR / "templates"
    env = Environment(loader=FileSystemLoader(str(template_dir)))
    failures: list[str] = []
    for path in sorted(template_dir.rglob("*.html")):
        name = path.relative_to(template_dir).as_posix()
        try:
            env.get_template(name)
        except Exception as exc:  # syntax/load validation
            failures.append(f"{name}: {exc}")
    if failures:
        fail("Template validation failed:\n  " + "\n  ".join(failures))
    ok("Templates load without syntax errors")



def check_job_definition_frontend() -> None:
    form_path = APP_DIR / "templates" / "job_definition_form.html"
    content = form_path.read_text(encoding="utf-8")
    required_frontend = [
        'id="insert-job-test-script"',
        'id="job-parameters-json"',
        'id="add-job-parameter"',
        'job-enabled-top',
        'jobdefs.own_script',
    ]
    missing = [item for item in required_frontend if item not in content]
    if missing:
        fail("Job-definition frontend validation failed; missing: " + ", ".join(missing))

    template_dir = APP_DIR / "templates" / "jobs"
    required_templates = {
        "powershell_wrapper.ps1": ["{{JobId}}", "{{CallbackUrl}}"],
        "python_wrapper.py.txt": ["{{JobId}}", "{{CallbackUrl}}"],
        "bash_wrapper.sh": ["{{JobId}}", "{{CallbackUrl}}", "${status}", "${code}"],
        "cmd_wrapper.cmd": ["{{JobId}}", "{{CallbackUrl}}"],
        "powershell_test.txt": ["{{AssetName}}", "{{Hostname}}"],
        "python_test.txt": ["{{AssetName}}", "{{Hostname}}"],
        "bash_test.txt": ["{{AssetName}}", "{{Hostname}}"],
        "cmd_test.txt": ["{{AssetName}}", "{{Hostname}}"],
    }
    failures: list[str] = []
    for filename, markers in required_templates.items():
        path = template_dir / filename
        if not path.is_file():
            failures.append(f"missing file: {filename}")
            continue
        template_content = path.read_text(encoding="utf-8")
        for marker in markers:
            if marker not in template_content:
                failures.append(f"{filename}: missing {marker}")
    if failures:
        fail("Job script template validation failed:\n  " + "\n  ".join(failures))
    ok("Job-definition backend generator safeguards are present")


def check_fastapi_import() -> None:
    # Import only. Do not start Uvicorn and do not connect to external services.
    sys.path.insert(0, str(ROOT))
    os.environ.setdefault("DATABASE_URL", "sqlite+pysqlite:///:memory:")
    try:
        from app.main import app  # noqa: F401
    except Exception as exc:
        fail(f"FastAPI application import failed: {type(exc).__name__}: {exc}")
    ok("FastAPI application imports")


def main() -> None:
    print("AssetManager release validation")
    check_version()
    check_repository_documentation()
    check_compile()
    check_translations()
    check_templates()
    check_job_definition_frontend()
    check_fastapi_import()
    print("All release checks passed.")


if __name__ == "__main__":
    main()
