"""Validation-related Claude hook script generators."""

from textwrap import dedent


def _obpi_completion_validator_script() -> str:
    """Return the OBPI completion validator hook script."""
    return dedent(
        """\
            #!/usr/bin/env python3
            \"\"\"OBPI Completion Validator Hook.

            PreToolUse hook that gates OBPI brief completion by checking ledger evidence
            before allowing status changes to 'Completed'.

            Aligned with airlineops canonical obpi-completion-validator.py.
            Uses ADR-local audit ledger ({adr-dir}/logs/obpi-audit.jsonl) as evidence source.

            Adaptations from canonical:
              - Path check: /obpis/OBPI- (gzkit) instead of /briefs/OBPI- (airlineops)
              - Lane resolution: checks both ADR markdown and frontmatter for Heavy lane

            Exit codes:
              0 - Allow operation
              2 - Block operation (evidence missing or attestation required)
            \"\"\"

            import json
            import re
            import sys
            from pathlib import Path


            def find_project_root() -> Path:
                \"\"\"Find the project root by looking for .gzkit directory.\"\"\"
                current = Path.cwd()
                while current != current.parent:
                    if (current / ".gzkit").is_dir():
                        return current
                    current = current.parent
                return Path.cwd()


            def find_parent_adr_dir(brief_path: Path) -> Path | None:
                \"\"\"Find the parent ADR directory from a brief path.

                gzkit stores briefs in /obpis/ (airlineops uses /briefs/).
                \"\"\"
                parent_dir = brief_path.parent
                if parent_dir.name not in ("obpis", "briefs"):
                    return None
                return parent_dir.parent


            def find_parent_adr_file(adr_dir: Path) -> Path | None:
                \"\"\"Find the parent ADR markdown file.\"\"\"
                for f in adr_dir.iterdir():
                    if (
                        f.name.startswith("ADR-")
                        and f.name.endswith(".md")
                        and re.match(r"ADR-[\\d.]+-", f.name)
                    ):
                        return f
                return None


            def check_status_change_to_completed(new_string: str) -> bool:
                \"\"\"Check if the edit changes status to Completed.\"\"\"
                status_patterns = [
                    r"\\*\\*(?:Brief\\s+)?Status:\\*\\*\\s*Completed",
                    r"^(?:Brief\\s+)?Status:\\s*Completed",
                    r"^\\|\\s*(?:Brief\\s+)?Status\\s*\\|\\s*Completed\\s*\\|",
                ]
                return any(
                    re.search(pattern, new_string, re.MULTILINE | re.IGNORECASE)
                    for pattern in status_patterns
                )


            def extract_obpi_id(file_path: str) -> str | None:
                \"\"\"Extract OBPI short ID (e.g. OBPI-0.14.0-04) from file path.\"\"\"
                match = re.search(r"(OBPI-[\\d.]+-\\d+)", file_path)
                return match.group(1) if match else None


            def extract_adr_id(obpi_id: str) -> str | None:
                \"\"\"Extract parent ADR ID from OBPI ID.

                e.g. OBPI-0.14.0-04 -> ADR-0.14.0
                \"\"\"
                match = re.match(r"OBPI-([\\d.]+)-\\d+", obpi_id)
                return f"ADR-{match.group(1)}" if match else None


            def is_foundation_adr(adr_id: str) -> bool:
                \"\"\"Check if ADR is foundation series (0.0.x).\"\"\"
                return adr_id.startswith("ADR-0.0.")


            def get_parent_adr_lane(adr_file: Path | None) -> str:
                \"\"\"Determine parent ADR's lane (Heavy or Lite).\"\"\"
                if adr_file is None:
                    return "unknown"
                try:
                    content = adr_file.read_text(encoding="utf-8")
                except OSError:
                    return "unknown"

                heavy_patterns = [
                    r"##\\s*Lane[\\s\\S]{0,50}Heavy",
                    r"\\*\\*Lane:\\*\\*\\s*Heavy",
                    r"Lane:\\s*Heavy",
                    r"\\|\\s*Lane\\s*\\|\\s*Heavy\\s*\\|",
                ]
                for pattern in heavy_patterns:
                    if re.search(pattern, content, re.IGNORECASE):
                        return "Heavy"

                return "Lite"


            def get_execution_mode(adr_file: Path | None) -> str:
                \"\"\"Read execution mode from ADR's ## Execution Mode section.\"\"\"
                if adr_file is None:
                    return "normal"
                try:
                    content = adr_file.read_text(encoding="utf-8")
                except OSError:
                    return "normal"

                exception_patterns = [
                    r"\\*\\*Mode:\\*\\*\\s*Exception",
                    r"Mode:\\s*Exception",
                ]
                for pattern in exception_patterns:
                    if re.search(pattern, content, re.IGNORECASE):
                        return "exception"

                return "normal"


            STRICT_PLACEHOLDERS = frozenset(
                {
                    "-",
                    "tbd",
                    "(none)",
                    "n/a",
                    "...",
                    "paste test output here",
                }
            )


            def has_substantive_implementation_summary(content: str) -> bool:
                \"\"\"Check for substantive bullet points in Implementation Summary.\"\"\"
                match = re.search(
                    r"^### Implementation Summary\\s*$([\\s\\S]*?)(?:^### |\\n---|\\Z)",
                    content,
                    flags=re.MULTILINE,
                )
                if not match:
                    return False
                section = match.group(1)
                # Primary: "- Key: value" bullets
                bullets = re.findall(r"^- [^:\\n]+:[ \\t]*(.+)$", section, flags=re.MULTILINE)
                if not bullets:
                    # Fallback: plain "- text" bullets
                    bullets = re.findall(r"^- \\s*(.+)$", section, flags=re.MULTILINE)
                for bullet in bullets:
                    normalized = bullet.strip().lower()
                    if normalized and normalized not in STRICT_PLACEHOLDERS:
                        return True
                return False


            def has_substantive_key_proof(content: str) -> bool:
                \"\"\"Check for substantive Key Proof section.\"\"\"
                for heading in ("Key Proof", "Verification", "Gate Evidence"):
                    match = re.search(
                        rf"^### {re.escape(heading)}\\s*$([\\s\\S]*?)(?:^### |\\n---|\\Z)",
                        content,
                        flags=re.MULTILINE,
                    )
                    if match:
                        body = match.group(1).strip()
                        if body and body.lower() not in STRICT_PLACEHOLDERS:
                            return True
                return False


            def has_valid_human_attestation(content: str) -> bool:
                \"\"\"Check that Human Attestation section has all three valid fields (#126).\"\"\"
                match = re.search(
                    r"^## Human Attestation\\s*$([\\s\\S]*?)(?:^## |\\n---|\\Z)",
                    content,
                    flags=re.MULTILINE,
                )
                if not match:
                    return False
                body = match.group(1)
                attestor_match = re.search(r"^- Attestor:\\s*(.+)$", body, flags=re.MULTILINE)
                if not attestor_match:
                    return False
                attestor_val = attestor_match.group(1).strip().strip("`").lower()
                placeholder_set = STRICT_PLACEHOLDERS | {"<name>", "todo", "none"}
                if not attestor_val or attestor_val in placeholder_set:
                    return False
                attestation_match = re.search(
                    r"^- Attestation:\\s*(.+)$", body, flags=re.MULTILINE
                )
                if not attestation_match:
                    return False
                attestation_val = attestation_match.group(1).strip().lower()
                if not attestation_val or attestation_val in STRICT_PLACEHOLDERS:
                    return False
                date_match = re.search(
                    r"^- Date:\\s*`?(\\d{4}-\\d{2}-\\d{2})`?$", body, flags=re.MULTILINE
                )
                return bool(date_match)


            def resolve_would_be_content(abs_path: Path, tool_input: dict) -> str:
                \"\"\"Resolve the file content that would result from the edit/write.\"\"\"
                content_field = tool_input.get("content")
                if content_field:
                    # Write tool: content IS the full new file
                    return content_field
                # Edit tool: read current file, apply old_string -> new_string
                old = tool_input.get("old_string", "")
                new = tool_input.get("new_string", "")
                try:
                    current = abs_path.read_text(encoding="utf-8")
                except OSError:
                    return new
                if old and old in current:
                    return current.replace(old, new, 1)
                return current


            def has_audit_evidence(adr_dir: Path, obpi_id: str) -> bool:
                \"\"\"Check if audit ledger entry exists for this OBPI in ADR-local ledger.\"\"\"
                ledger_file = adr_dir / "logs" / "obpi-audit.jsonl"

                if not ledger_file.exists():
                    return False

                try:
                    with ledger_file.open("r", encoding="utf-8") as f:
                        for line in f:
                            line = line.strip()
                            if not line:
                                continue
                            try:
                                entry = json.loads(line)
                                entry_type = entry.get("type", "")
                                entry_obpi = entry.get("obpi_id", "")
                                if entry_obpi == obpi_id and entry_type in (
                                    "obpi-audit",
                                    "obpi-completion",
                                ):
                                    return True
                            except json.JSONDecodeError:
                                continue
                except OSError:
                    return False

                return False


            def has_human_attestation(adr_dir: Path, obpi_id: str) -> bool:
                \"\"\"Check if human attestation exists in ADR-local ledger for this OBPI.\"\"\"
                ledger_file = adr_dir / "logs" / "obpi-audit.jsonl"

                if not ledger_file.exists():
                    return False

                try:
                    with ledger_file.open("r", encoding="utf-8") as f:
                        for line in f:
                            line = line.strip()
                            if not line:
                                continue
                            try:
                                entry = json.loads(line)
                                entry_obpi = entry.get("obpi_id", "")
                                if entry_obpi == obpi_id:
                                    evidence = entry.get("evidence", {})
                                    if evidence.get("human_attestation"):
                                        return True
                                    if entry.get("attestation_type") == "human":
                                        return True
                            except json.JSONDecodeError:
                                continue
                except OSError:
                    return False

                return False


            def main():
                \"\"\"Validate and gate OBPI completion.\"\"\"
                try:
                    input_data = json.load(sys.stdin)
                except json.JSONDecodeError:
                    sys.exit(0)

                tool_input = input_data.get("tool_input", {})
                file_path = tool_input.get("file_path", "")
                new_string = tool_input.get("new_string", "") or tool_input.get("content", "")

                # Normalize path
                try:
                    project_root = find_project_root()
                    abs_path = Path(file_path).resolve()
                    rel_path = abs_path.relative_to(project_root)
                    rel_str = rel_path.as_posix()
                except (ValueError, TypeError):
                    sys.exit(0)

                # 1. Is this an OBPI brief file? (gzkit uses /obpis/, airlineops uses /briefs/)
                if "/obpis/OBPI-" not in rel_str or not rel_str.endswith(".md"):
                    sys.exit(0)

                # 2. Is this changing status to Completed?
                #    If old_string already contains Completed, the file is already in that state
                #    and this edit is not introducing a status transition — allow it through.
                old_string = tool_input.get("old_string", "")
                if not check_status_change_to_completed(new_string):
                    sys.exit(0)
                if old_string and check_status_change_to_completed(old_string):
                    sys.exit(0)

                # 3. Extract identifiers
                obpi_id = extract_obpi_id(rel_str)
                if not obpi_id:
                    sys.exit(0)

                adr_id = extract_adr_id(obpi_id)
                if not adr_id:
                    sys.exit(0)

                # 4. Find parent ADR directory and file
                adr_dir = find_parent_adr_dir(abs_path)
                if not adr_dir or not adr_dir.exists():
                    sys.exit(0)

                adr_file = find_parent_adr_file(adr_dir)

                # 4b. Resolve lane early so content quality checks can gate attestation (#126)
                is_foundation = is_foundation_adr(adr_id)
                parent_lane = get_parent_adr_lane(adr_file)
                execution_mode = get_execution_mode(adr_file)
                requires_human = execution_mode != "exception" and (
                    is_foundation or parent_lane == "Heavy"
                )

                # 5. Check brief content quality (hard block)
                would_be = resolve_would_be_content(abs_path, tool_input)
                brief_blocks = []
                if not has_substantive_implementation_summary(would_be):
                    brief_blocks.append(
                        "  - Missing or non-substantive 'Implementation Summary' "
                        "(requires bullet format: '- Key: value')"
                    )
                if not has_substantive_key_proof(would_be):
                    brief_blocks.append(
                        "  - Missing or non-substantive 'Key Proof' "
                        "(requires test command output or verification evidence)"
                    )
                if requires_human and not has_valid_human_attestation(would_be):
                    brief_blocks.append(
                        "  - Missing or malformed 'Human Attestation' section "
                        "(requires Attestor, Attestation text, and ISO date YYYY-MM-DD)"
                    )
                if brief_blocks:
                    details = "\\n".join(brief_blocks)
                    print(
                        f"\\n\\u26d4 BLOCKED: Cannot mark {obpi_id} as Completed.\\n"
                        f"\\n"
                        f"Brief content quality checks failed:\\n"
                        f"{details}\\n"
                        f"\\n"
                        f"REQUIRED: Add substantive content to these sections before "
                        f"marking the OBPI as Completed.\\n",
                        file=sys.stderr,
                    )
                    sys.exit(2)

                # 6. Check for audit evidence in ADR-local ledger
                if not has_audit_evidence(adr_dir, obpi_id):
                    print(
                        f"\\n\\u26d4 BLOCKED: Cannot mark {obpi_id} as Completed.\\n"
                        f"\\n"
                        f"No audit evidence found in {adr_dir.name}/logs/obpi-audit.jsonl\\n"
                        f"\\n"
                        f"REQUIRED: Run gz-obpi-audit first to verify and record evidence:\\n"
                        f"  /gz-obpi-audit {obpi_id}\\n",
                        file=sys.stderr,
                    )
                    sys.exit(2)

                # 7. Check attestation requirements (lane resolved in step 4b)
                if execution_mode == "exception":
                    # Exception mode: self-close allowed. Audit evidence already
                    # validated above. Human reviews at ADR closeout.
                    sys.exit(0)

                if requires_human and not has_human_attestation(adr_dir, obpi_id):
                    lane_reason = "Foundation (0.0.x)" if is_foundation else "Heavy lane"
                    print(
                        f"\\n\\u26d4 BLOCKED: {obpi_id} requires human attestation.\\n"
                        f"\\n"
                        f"Parent {adr_id} is {lane_reason}.\\n"
                        f"Per AGENTS.md, OBPIs under Heavy/Foundation ADRs "
                        f"inherit attestation rigor.\\n"
                        f"\\n"
                        f"REQUIRED: Present evidence and receive human attestation:\\n"
                        f"  1. Show verification command outputs and test results\\n"
                        f"  2. Wait for human to respond 'attested' or 'approved'\\n"
                        f"  3. Record attestation in ledger, then complete\\n",
                        file=sys.stderr,
                    )
                    sys.exit(2)

                # All validations passed
                sys.exit(0)


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


def _ledger_writer_script() -> str:
    """Return the ledger writer hook script."""
    return dedent(
        """\
            #!/usr/bin/env python3
            \"\"\"gzkit ledger writer and validator hook for claude.

            This hook records governance artifact edits and enforces completion gates.
            \"\"\"

            import json
            import os
            import sys
            from pathlib import Path


            def find_project_root() -> Path:
                \"\"\"Find the project root by looking for .gzkit directory.\"\"\"
                current = Path.cwd()
                while current != current.parent:
                    if (current / ".gzkit").is_dir():
                        return current
                    current = current.parent
                return Path.cwd()


            def main() -> int:
                \"\"\"Main hook entry point.\"\"\"
                # Read tool use info from stdin (Claude Code format)
                try:
                    input_data = json.load(sys.stdin)
                except json.JSONDecodeError:
                    return 0  # Silently continue if no valid input

                # Extract file path from tool use
                tool_name = input_data.get("tool_name", "")
                tool_input = input_data.get("tool_input", {})

                if tool_name not in ("Edit", "Write"):
                    return 0

                file_path = tool_input.get("file_path", "")
                if not file_path:
                    return 0

                # Make path relative to project root
                project_root = find_project_root()
                try:
                    rel_path = Path(file_path).relative_to(project_root)
                except ValueError:
                    return 0

                # Import gzkit and record edit/validate
                sys.path.insert(0, str(project_root / "src"))
                try:
                    from gzkit.hooks.core import record_artifact_edit

                    session = os.environ.get("CLAUDE_SESSION_ID") or os.environ.get(
                        "COPILOT_SESSION_ID"
                    )

                    # This will trigger validation and raise if it fails
                    record_artifact_edit(project_root, str(rel_path), session)

                except ImportError:
                    pass  # gzkit not installed, skip
                except Exception as exc:
                    print(f"\\n[GOVERNANCE BLOCK] {exc}\\n", file=sys.stderr)
                    return 1

                return 0


            if __name__ == "__main__":
                sys.exit(main())
            """
    )


def _control_surface_sync_script() -> str:
    """Return the post-edit control-surface sync hook script."""
    return dedent(
        """\
            #!/usr/bin/env python3
            \"\"\"Control Surface Sync Hook.

            PostToolUse hook that runs `gz agent sync control-surfaces` after edits
            to canonical governance files in .gzkit/.

            .gzkit/ holds master versions of all control surfaces:
              - skills/ — universal, mirrored identically across vendors
              - hooks/ — master source, rendered to platform-specific forms
              - rules/ — master source, rendered to platform-specific forms

            Exit codes:
              0 - Always (non-blocking; sync failures do not prevent edits)
            \"\"\"

            import json
            import subprocess
            import sys
            from contextlib import suppress
            from pathlib import Path

            TIMEOUT_SECONDS = 30

            CONTROL_SURFACE_PATTERNS = (".gzkit/",)


            def main():
                \"\"\"Run control-surface sync if the edited file is a canonical source.\"\"\"
                try:
                    input_data = json.load(sys.stdin)
                except json.JSONDecodeError:
                    sys.exit(0)

                tool_input = input_data.get("tool_input", {})
                file_path = tool_input.get("file_path", "")

                cwd = input_data.get("cwd", "")
                try:
                    target = Path(file_path)
                    if not target.is_absolute() and cwd:
                        target = Path(cwd) / target
                    target = target.resolve()
                    # Find project root
                    project_root = Path(cwd).resolve() if cwd else Path.cwd()
                    current = project_root
                    while current != current.parent:
                        if (current / ".gzkit").is_dir():
                            project_root = current
                            break
                        current = current.parent
                    rel_path = target.relative_to(project_root).as_posix()
                except (ValueError, TypeError, OSError):
                    sys.exit(0)

                matches = any(
                    rel_path.startswith(p) or rel_path == p.rstrip("/")
                    for p in CONTROL_SURFACE_PATTERNS
                )
                if not matches:
                    sys.exit(0)

                print(f"Control surface edited: {rel_path}", file=sys.stderr)
                print("Running gz agent sync control-surfaces...", file=sys.stderr)

                with suppress(FileNotFoundError, subprocess.TimeoutExpired, OSError):
                    result = subprocess.run(
                        ["uv", "run", "gz", "agent", "sync", "control-surfaces"],
                        capture_output=True,
                        text=True,
                        timeout=TIMEOUT_SECONDS,
                        cwd=str(project_root),
                    )
                    if result.returncode == 0:
                        print("Sync complete.", file=sys.stderr)
                    else:
                        print(f"Sync failed (exit {result.returncode}).", file=sys.stderr)
                        if result.stderr:
                            print(result.stderr[:500], file=sys.stderr)

                sys.exit(0)


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