#!/usr/bin/env python3
# retoor <retoor@molodetz.nl>

import json
import re
import sys
from pathlib import Path

CONFIRMATION_TOKEN = "I-HAVE-CONFIRMED-PRODUCTION-DB-ACCESS"

PRODUCTION_PATHS = re.compile(
    r"data/(devplace\.db|devii_tasks\.db|devii_lessons\.db|keys|uploads"
    r"|attachments|project_files|backups)\b"
)
PRODUCTION_DB_FILE = re.compile(r"\bdevplace\.db\b")
MANAGEMENT_CLI = re.compile(r"(?:^|[;&|(]\s*|\s)(?:[\w./-]*/)?devplace\s+(?!-)")
PYTHON_INVOCATION = re.compile(r"(?:^|[;&|(\s])(?:[\w./-]*/)?python[0-9.]*(?:\s|$)")
DATABASE_OVERRIDE = re.compile(r"DEVPLACE_DATABASE_URL\s*=\s*[\"']?(\S+?)[\"']?(?:\s|$)")
DATABASE_ASSIGNMENT = re.compile(r"DEVPLACE_DATABASE_URL[\"'\]\s]*[=,]")
MODULE_INVOCATION = re.compile(r"-m\s+devplacepy")
INLINE_CODE = re.compile(r"-c\s+(?P<quote>[\"'])(?P<code>.*?)(?P=quote)", re.DOTALL)
SCRIPT_PATH = re.compile(r"(?:^|\s)(?P<path>[\w./~-]+\.py)(?:\s|$)")
IMPORT_GATE = re.compile(
    r"^from devplacepy\.main import app\s*;?\s*(?:print\([^)]*\)\s*;?\s*)?$"
)
TEST_RUNNER = re.compile(r"\bpytest\b|\bmake\s+(test|test-[\w-]+)\b")
SERVER_TARGET = re.compile(r"\bmake\s+(dev|prod|docker-[\w-]+|ppy)\b")


APPLICATION_IMPORT = re.compile(
    r"(?:^|[\s;])(?:from|import)\s+devplacepy\b"
    r"|import_module\s*\(\s*[\"']devplacepy",
    re.MULTILINE,
)


def reaches_application(text: str) -> bool:
    return bool(APPLICATION_IMPORT.search(text))


def overrides_the_database(text: str) -> bool:
    match = DATABASE_OVERRIDE.search(text)
    if not match:
        return False
    return "data/devplace.db" not in match.group(1)


def source_targets_a_scratch_database(source: str) -> bool:
    if PRODUCTION_DB_FILE.search(source):
        return False
    return bool(DATABASE_ASSIGNMENT.search(source))


def script_is_safe(command: str) -> bool | None:
    match = SCRIPT_PATH.search(command)
    if not match:
        return None
    path = Path(match.group("path")).expanduser()
    try:
        body = path.read_text(errors="replace")
    except OSError:
        return None
    if not reaches_application(body):
        return True
    return source_targets_a_scratch_database(body)


def hazard_in(command: str) -> str:
    if CONFIRMATION_TOKEN in command:
        return ""
    if TEST_RUNNER.search(command) or SERVER_TARGET.search(command):
        return ""
    if PRODUCTION_DB_FILE.search(command) or PRODUCTION_PATHS.search(command):
        return "it names the production database or a production data directory"
    if MANAGEMENT_CLI.search(command):
        return "the devplace management CLI operates on the production database"
    if not PYTHON_INVOCATION.search(command):
        return ""
    if overrides_the_database(command):
        return ""
    if MODULE_INVOCATION.search(command):
        return "it runs a devplacepy module with no DEVPLACE_DATABASE_URL override"
    inline = INLINE_CODE.search(command)
    if inline:
        code = inline.group("code").strip()
        if not reaches_application(code):
            return ""
        if IMPORT_GATE.match(code):
            return ""
        return "it imports devplacepy inline with no DEVPLACE_DATABASE_URL override"
    safe = script_is_safe(command)
    if safe is None:
        return ""
    if safe:
        return ""
    return "the script imports devplacepy with no DEVPLACE_DATABASE_URL override"


def refuse(reason: str) -> dict:
    return {
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": (
                f"Blocked: this command reaches the production database because {reason}. "
                "The production database is never touched without the user's explicit, "
                "stated confirmation. Stop, tell the user exactly what the command would "
                "read or write, and ask them to confirm in their own words. Only after "
                f"they have done so may the command carry the literal token "
                f"{CONFIRMATION_TOKEN}, which still raises a permission prompt they must "
                "approve. Never add that token on your own initiative. Alternatives that "
                "need no confirmation: set DEVPLACE_DATABASE_URL to a scratch database, "
                "or run the test suite."
            ),
        }
    }


def confirm(reason: str) -> dict:
    return {
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "ask",
            "permissionDecisionReason": (
                "This command carries the production-database confirmation token and "
                f"reaches the production database because {reason}. Approve only if you "
                "asked for this."
            ),
        }
    }


def main() -> None:
    try:
        payload = json.load(sys.stdin)
    except (json.JSONDecodeError, ValueError):
        sys.exit(0)
    command = (payload.get("tool_input") or {}).get("command") or ""
    if not command:
        sys.exit(0)
    if CONFIRMATION_TOKEN in command:
        stripped = command.replace(CONFIRMATION_TOKEN, "")
        reason = hazard_in(stripped)
        if reason:
            print(json.dumps(confirm(reason)))
        sys.exit(0)
    reason = hazard_in(command)
    if reason:
        print(json.dumps(refuse(reason)))
    sys.exit(0)


if __name__ == "__main__":
    main()
