#!/usr/bin/env python3
"""Matrix/Conduit Scaffold Validator — Issue #183 Acceptance Proof

Validates that infra/matrix/ contains a complete, well-formed deployment scaffold.
Run this after any scaffold change to ensure #183 acceptance criteria remain met.

Usage:
    python3 infra/matrix/scripts/validate-scaffold.py
    python3 infra/matrix/scripts/validate-scaffold.py --json

Exit codes:
    0 = all checks passed
    1 = one or more checks failed
"""

import argparse
import json
import os
import re
import subprocess
import sys
from pathlib import Path

try:
    import yaml
    HAS_YAML = True
except ImportError:
    HAS_YAML = False


class Validator:
    def __init__(self, base_dir: Path):
        self.base_dir = base_dir.resolve()
        self.checks = []
        self.passed = 0
        self.failed = 0

    def _add(self, name: str, status: bool, detail: str):
        self.checks.append({"name": name, "status": "PASS" if status else "FAIL", "detail": detail})
        if status:
            self.passed += 1
        else:
            self.failed += 1

    def require_files(self):
        """Check that all required scaffold files exist."""
        required = [
            "README.md",
            "prerequisites.md",
            "docker-compose.yml",
            "conduit.toml",
            ".env.example",
            "deploy-matrix.sh",
            "host-readiness-check.sh",
            "caddy/Caddyfile",
            "scripts/deploy-conduit.sh",
            "docs/RUNBOOK.md",
        ]
        missing = []
        for rel in required:
            path = self.base_dir / rel
            if not path.exists():
                missing.append(rel)
        self._add(
            "Required files present",
            len(missing) == 0,
            f"Missing: {missing}" if missing else f"All {len(required)} files found",
        )

    def docker_compose_valid(self):
        """Validate docker-compose.yml is syntactically valid YAML."""
        path = self.base_dir / "docker-compose.yml"
        if not path.exists():
            self._add("docker-compose.yml valid YAML", False, "File does not exist")
            return
        try:
            with open(path, "r") as f:
                content = f.read()
            if HAS_YAML:
                yaml.safe_load(content)
            else:
                # Basic YAML brace balance check
                if content.count("{") != content.count("}"):
                    raise ValueError("Brace mismatch")
            # Must reference conduit image or build
            has_conduit = "conduit" in content.lower()
            self._add(
                "docker-compose.yml valid YAML",
                has_conduit,
                "Valid YAML and references Conduit" if has_conduit else "Valid YAML but missing Conduit reference",
            )
        except Exception as e:
            self._add("docker-compose.yml valid YAML", False, str(e))

    def conduit_toml_valid(self):
        """Validate conduit.toml has required sections."""
        path = self.base_dir / "conduit.toml"
        if not path.exists():
            self._add("conduit.toml required keys", False, "File does not exist")
            return
        with open(path, "r") as f:
            content = f.read()
        required_keys = ["server_name", "port", "[database]"]
        missing = [k for k in required_keys if k not in content]
        self._add(
            "conduit.toml required keys",
            len(missing) == 0,
            f"Missing keys: {missing}" if missing else "Required keys present",
        )

    def env_example_complete(self):
        """Validate .env.example has required variables."""
        path = self.base_dir / ".env.example"
        if not path.exists():
            self._add(".env.example required variables", False, "File does not exist")
            return
        with open(path, "r") as f:
            content = f.read()
        required_vars = ["MATRIX_DOMAIN", "ADMIN_USER", "ADMIN_PASSWORD"]
        missing = [v for v in required_vars if v not in content]
        self._add(
            ".env.example required variables",
            len(missing) == 0,
            f"Missing vars: {missing}" if missing else "Required variables present",
        )

    def shell_scripts_executable(self):
        """Check that shell scripts are executable and pass bash -n."""
        scripts = [
            self.base_dir / "deploy-matrix.sh",
            self.base_dir / "host-readiness-check.sh",
            self.base_dir / "scripts" / "deploy-conduit.sh",
        ]
        errors = []
        for script in scripts:
            if not script.exists():
                errors.append(f"{script.name}: missing")
                continue
            if not os.access(script, os.X_OK):
                errors.append(f"{script.name}: not executable")
            result = subprocess.run(["bash", "-n", str(script)], capture_output=True, text=True)
            if result.returncode != 0:
                errors.append(f"{script.name}: syntax error — {result.stderr.strip()}")
        self._add(
            "Shell scripts executable & valid",
            len(errors) == 0,
            "; ".join(errors) if errors else f"All {len(scripts)} scripts OK",
        )

    def caddyfile_well_formed(self):
        """Check Caddyfile has expected tokens."""
        path = self.base_dir / "caddy" / "Caddyfile"
        if not path.exists():
            self._add("Caddyfile well-formed", False, "File does not exist")
            return
        with open(path, "r") as f:
            content = f.read()
        has_reverse_proxy = "reverse_proxy" in content
        has_tls = "tls" in content.lower() or "acme" in content.lower() or "auto" in content.lower()
        has_well_known = ".well-known" in content or "matrix" in content.lower()
        ok = has_reverse_proxy and has_well_known
        detail = []
        if not has_reverse_proxy:
            detail.append("missing reverse_proxy directive")
        if not has_well_known:
            detail.append("missing .well-known/matrix routing")
        self._add(
            "Caddyfile well-formed",
            ok,
            "Well-formed" if ok else f"Issues: {', '.join(detail)}",
        )

    def runbook_links_valid(self):
        """Check docs/RUNBOOK.md has links to #166 and #183."""
        path = self.base_dir / "docs" / "RUNBOOK.md"
        if not path.exists():
            self._add("RUNBOOK.md issue links", False, "File does not exist")
            return
        with open(path, "r") as f:
            content = f.read()
        has_166 = "#166" in content or "166" in content
        has_183 = "#183" in content or "183" in content
        ok = has_166 and has_183
        self._add(
            "RUNBOOK.md issue links",
            ok,
            "Links to #166 and #183" if ok else "Missing issue continuity links",
        )

    def run_all(self):
        self.require_files()
        self.docker_compose_valid()
        self.conduit_toml_valid()
        self.env_example_complete()
        self.shell_scripts_executable()
        self.caddyfile_well_formed()
        self.runbook_links_valid()

    def report(self, json_mode: bool = False):
        if json_mode:
            print(json.dumps({
                "base_dir": str(self.base_dir),
                "passed": self.passed,
                "failed": self.failed,
                "checks": self.checks,
            }, indent=2))
        else:
            print(f"Matrix/Conduit Scaffold Validator")
            print(f"Base: {self.base_dir}")
            print(f"Checks: {self.passed} passed, {self.failed} failed\n")
            for c in self.checks:
                icon = "✅" if c["status"] == "PASS" else "❌"
                print(f"{icon} {c['name']:<40} {c['detail']}")
            print(f"\n{'SUCCESS' if self.failed == 0 else 'FAILURE'} — {self.passed}/{self.passed+self.failed} checks passed")


def main():
    parser = argparse.ArgumentParser(description="Validate Matrix/Conduit deployment scaffold")
    parser.add_argument("--json", action="store_true", help="Output JSON report")
    parser.add_argument("--base", default="infra/matrix", help="Path to scaffold directory")
    args = parser.parse_args()

    base = Path(args.base)
    if not base.exists():
        # Try relative to script location
        script_dir = Path(__file__).resolve().parent
        base = script_dir.parent

    validator = Validator(base)
    validator.run_all()
    validator.report(json_mode=args.json)
    sys.exit(0 if validator.failed == 0 else 1)


if __name__ == "__main__":
    main()
