from pathlib import Path
import re
import subprocess
import tempfile
import unittest


ROOT = Path(__file__).resolve().parents[1]
PHP_TEST = ROOT / "tests" / "php" / "diagnostic_log_test.php"
PRODUCTION_PHP = [ROOT / "bee_transaction_log.php"] + [
    path
    for folder in (ROOT / "api", ROOT / "classes")
    for path in folder.rglob("*.php")
    if "vendor" not in path.parts
]


def legacy_test_output_offenders(root: Path) -> list[str]:
    offenders = []
    excluded_logrotate = Path("deploy/logrotate/razgar-bee")
    for path in root.rglob("*"):
        if not path.is_file():
            continue
        relative = path.relative_to(root)
        if relative.parts[0] == "tests" or relative == excluded_logrotate:
            continue
        try:
            contents = path.read_text(encoding="utf-8")
        except UnicodeDecodeError:
            continue
        if "test_output_" in contents:
            offenders.append(relative.as_posix())
    return sorted(offenders)


class DiagnosticLogContractTests(unittest.TestCase):
    def test_logger_is_loaded_before_error_handler(self):
        classes = (ROOT / "classes" / "classes.inc").read_text(encoding="utf-8")
        self.assertIn('require_once("BeeDiagnosticLog.php");', classes)
        self.assertLess(
            classes.index('require_once("BeeDiagnosticLog.php");'),
            classes.index('require_once("Error.php");'),
        )

    def test_php_errors_use_managed_log_path(self):
        init = (ROOT / "init.inc.php").read_text(encoding="utf-8")
        self.assertIn("'/var/log/bee_logs/php-error.log'", init)
        self.assertNotIn("'/var/www/html/debug.txt'", init)

    def test_diagnostic_directory_override_is_cli_only(self):
        logger = (ROOT / "classes" / "BeeDiagnosticLog.php").read_text(encoding="utf-8")
        self.assertIn("PHP_SAPI !== 'cli'", logger)
        self.assertIn("private const DEFAULT_DIRECTORY = '/var/log/bee_logs';", logger)
        self.assertIn("return self::DEFAULT_DIRECTORY;", logger)
        self.assertNotIn('WORKER_TEST_LOG_DIR', logger)

    def test_logger_swallows_filesystem_and_lock_warnings(self):
        logger = (ROOT / "classes" / "BeeDiagnosticLog.php").read_text(encoding="utf-8")
        for function in (
            'is_dir',
            'is_writable',
            'fopen',
            'flock',
            'ftruncate',
            'fflush',
            'fseek',
            'fwrite',
            'fclose',
        ):
            self.assertIsNone(
                re.search(rf'(?<!@)\b{function}\s*\(', logger),
                msg=f'{function} warning is not suppressed',
            )

    def test_candle_has_no_empty_else_blocks(self):
        candle = (ROOT / "classes" / "Candle.php").read_text(encoding="utf-8")
        self.assertIsNone(re.search(r'}\s*else\s*{\s*}', candle))

    def test_no_production_php_file_mentions_legacy_test_output_files(self):
        offenders = sorted(
            path.relative_to(ROOT).as_posix()
            for path in PRODUCTION_PHP
            if "test_output_" in path.read_text(encoding="utf-8")
        )
        self.assertEqual([], offenders)

    def test_no_tracked_non_test_source_mentions_legacy_test_output_files(self):
        self.assertEqual([], legacy_test_output_offenders(ROOT))

    def test_legacy_test_output_scan_detects_only_included_text_sources(self):
        with tempfile.TemporaryDirectory() as temporary:
            root = Path(temporary)
            (root / "included.php").write_text("test_output_", encoding="utf-8")
            tests = root / "tests"
            tests.mkdir()
            (tests / "fixture.py").write_text("test_output_", encoding="utf-8")
            logrotate = root / "deploy" / "logrotate"
            logrotate.mkdir(parents=True)
            (logrotate / "razgar-bee").write_text("test_output_", encoding="utf-8")
            (root / "binary.dat").write_bytes(b"\xfftest_output_")
            self.assertEqual(["included.php"], legacy_test_output_offenders(root))

    def test_cli_logger_cases(self):
        for case in ("disabled", "selected", "uncapped", "hard-bound", "error-selected", "unavailable"):
            with self.subTest(case=case):
                completed = subprocess.run(
                    ["php", str(PHP_TEST), case],
                    cwd=ROOT,
                    text=True,
                    capture_output=True,
                    check=False,
                )
                self.assertEqual(
                    0,
                    completed.returncode,
                    msg=completed.stdout + completed.stderr,
                )


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