from __future__ import annotations

import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch

from govoplan_core.devserver import build_reload_dirs, redacted_database_url


class DevserverSmokeTests(unittest.TestCase):
    def test_reload_dirs_can_be_limited_to_selected_modules(self) -> None:
        with tempfile.TemporaryDirectory(prefix="govoplan-reload-") as directory:
            root = Path(directory)
            core_root = root / "core"
            calendar_root = root / "calendar"
            mail_root = root / "mail"
            extra_root = root / "extra"
            for path in (core_root, calendar_root, mail_root, extra_root):
                path.mkdir()

            manifests = (
                SimpleNamespace(id="calendar"),
                SimpleNamespace(id="mail"),
            )
            config = SimpleNamespace(enabled_modules=(), manifest_factories=())
            registry = SimpleNamespace(manifests=lambda: manifests)

            def manifest_roots(manifest: object) -> tuple[Path, ...]:
                return (
                    calendar_root
                    if getattr(manifest, "id") == "calendar"
                    else mail_root,
                )

            with (
                patch(
                    "govoplan_core.devserver._config_source_roots",
                    return_value=(core_root,),
                ),
                patch(
                    "govoplan_core.devserver._entry_point_source_roots",
                    return_value=(),
                ),
                patch(
                    "govoplan_core.devserver._manifest_source_roots",
                    side_effect=manifest_roots,
                ),
            ):
                broad = build_reload_dirs(config, registry=registry)
                focused = build_reload_dirs(
                    config,
                    registry=registry,
                    module_ids=("calendar",),
                    extra_dirs=(str(extra_root),),
                )
                core_only = build_reload_dirs(
                    config,
                    registry=registry,
                    module_ids=(),
                )

            self.assertEqual(
                {str(core_root), str(calendar_root), str(mail_root)},
                set(broad),
            )
            self.assertEqual(
                {str(core_root), str(calendar_root), str(extra_root)},
                set(focused),
            )
            self.assertEqual([str(core_root)], core_only)

    def test_reload_dirs_reject_disabled_module_selector(self) -> None:
        config = SimpleNamespace(enabled_modules=(), manifest_factories=())
        registry = SimpleNamespace(
            manifests=lambda: (SimpleNamespace(id="calendar"),)
        )
        with (
            patch(
                "govoplan_core.devserver._config_source_roots",
                return_value=(),
            ),
            self.assertRaisesRegex(SystemExit, "mail"),
        ):
            build_reload_dirs(
                config,
                registry=registry,
                module_ids=("mail",),
            )

    def test_database_url_redaction_hides_passwords(self) -> None:
        rendered = redacted_database_url(
            "postgresql+psycopg://govoplan:database-secret@db.example.test/govoplan"
        )

        self.assertEqual(
            rendered,
            "postgresql+psycopg://govoplan:***@db.example.test/govoplan",
        )
        self.assertNotIn("database-secret", rendered)
        self.assertEqual(redacted_database_url("://database-secret"), "<redacted>")

    def test_smoke_mode_bootstraps_missing_local_sqlite_database(self) -> None:
        repo_root = Path(__file__).resolve().parents[1]
        src_root = repo_root / "src"
        with tempfile.TemporaryDirectory(prefix="govoplan-devserver-smoke-") as directory:
            runtime_root = Path(directory)
            env = os.environ.copy()
            for key in (
                "DATABASE_URL",
                "DEV_BOOTSTRAP_ENABLED",
                "GOVOPLAN_DEV_DATABASE_BACKEND",
                "GOVOPLAN_DEV_DATABASE_URL",
                "GOVOPLAN_DEV_DATABASE_URL_PGTOOLS",
                "GOVOPLAN_DATABASE_URL_PGTOOLS",
                "GOVOPLAN_SERVER_CONFIG",
                "FILE_STORAGE_LOCAL_ROOT",
                "MOCK_MAILBOX_DIR",
            ):
                env.pop(key, None)
            env["APP_ENV"] = "dev"
            env["CELERY_ENABLED"] = "false"
            env["ENABLED_MODULES"] = "access"
            env["GOVOPLAN_DEV_DATABASE_BACKEND"] = "sqlite"
            env["PYTHONPATH"] = str(src_root) + (os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "")

            result = subprocess.run(
                [sys.executable, "-m", "govoplan_core.devserver", "--smoke", "--no-reload"],
                cwd=runtime_root,
                env=env,
                text=True,
                capture_output=True,
                check=False,
            )

            self.assertEqual(result.returncode, 0, result.stderr or result.stdout)
            db_path = runtime_root / "runtime" / "govoplan-dev.db"
            self.assertTrue(db_path.exists(), result.stdout)
            self.assertGreater(db_path.stat().st_size, 0, result.stdout)
            self.assertIn(f"Runtime root: {runtime_root}", result.stdout)
            self.assertIn(f"Database: sqlite:///{db_path}", result.stdout)
            self.assertIn("Dev bootstrap for missing SQLite DB: enabled", result.stdout)
            self.assertIn("Smoke check: OK", result.stdout)


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