from __future__ import annotations

import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from ansible.errors import AnsibleError

from plugins.lookup.users import LookupModule, _reset_cache_for_tests
from utils.cache import base as runtime_data_base
from utils.cache.yaml import dump_yaml_str
from utils.paths import FILE_TOKENS
from utils.roles.mapping import ROLE_FILE_META_USERS


def _write_users(base_dir: Path, role_name: str, users: dict) -> None:
    """Write meta/users.yml — file root IS the users map."""
    users_path = base_dir / "roles" / role_name / ROLE_FILE_META_USERS
    users_path.parent.mkdir(parents=True, exist_ok=True)
    users_path.write_text(dump_yaml_str(users), encoding="utf-8")


class TestUsersLookup(unittest.TestCase):
    def setUp(self) -> None:
        self.lookup = LookupModule()
        self._cwd = str(Path.cwd())
        self._tmpdir = tempfile.TemporaryDirectory()
        self._tmp = Path(self._tmpdir.name)
        os.chdir(self._tmp)
        _reset_cache_for_tests()

        _write_users(
            self._tmp,
            "identity",
            {
                "alice": {
                    "username": "alice",
                    "email": "alice@example.org",
                    "authorized_keys": ["ssh-ed25519 AAAA alice@example.org"],
                }
            },
        )
        self.lookup._templar = None

    def tearDown(self) -> None:
        _reset_cache_for_tests()
        os.chdir(self._cwd)
        self._tmpdir.cleanup()

    def test_returns_full_mapping(self) -> None:
        result = self.lookup.run([], variables={}, roles_dir=str(self._tmp / "roles"))[
            0
        ]
        self.assertIn("alice", result)
        self.assertEqual(result["alice"]["email"], "alice@example.org")

    def test_returns_single_entry(self) -> None:
        result = self.lookup.run(
            ["alice"],
            variables={},
            roles_dir=str(self._tmp / "roles"),
        )[0]
        self.assertEqual(result["username"], "alice")

    def test_applies_inventory_override(self) -> None:
        result = self.lookup.run(
            ["alice"],
            variables={"users": {"alice": {"email": "override@example.org"}}},
            roles_dir=str(self._tmp / "roles"),
        )[0]
        self.assertEqual(result["email"], "override@example.org")
        self.assertEqual(result["username"], "alice")

    def test_ignores_non_mapping_runtime_users_placeholder(self) -> None:
        result = self.lookup.run(
            ["alice"],
            variables={"users": "alice"},
            roles_dir=str(self._tmp / "roles"),
        )[0]
        self.assertEqual(result["email"], "alice@example.org")
        self.assertEqual(result["username"], "alice")

    def test_missing_entry_raises_without_default(self) -> None:
        with self.assertRaises(AnsibleError):
            self.lookup.run(
                ["bob"],
                variables={},
                roles_dir=str(self._tmp / "roles"),
            )

    def test_missing_entry_returns_default_when_provided(self) -> None:
        default_value = {"fallback": True}
        result = self.lookup.run(
            ["bob", default_value],
            variables={},
            roles_dir=str(self._tmp / "roles"),
        )[0]
        self.assertEqual(result, default_value)

    def test_renders_templated_defaults_with_templar(self) -> None:
        _write_users(
            self._tmp,
            "templated",
            {
                "blackhole": {
                    "email": "blackhole@{{ SYSTEM_EMAIL_DOMAIN }}",
                    "password": "{{ 42 | strong_password }}",
                }
            },
        )
        _reset_cache_for_tests()
        self.lookup._templar = _SplitTemplar(
            {"SYSTEM_EMAIL_DOMAIN": "mail.example.org"}
        )

        result = self.lookup.run(
            ["blackhole"],
            variables={"SYSTEM_EMAIL_DOMAIN": "mail.example.org"},
            roles_dir=str(self._tmp / "roles"),
        )[0]

        self.assertEqual(result["email"], "blackhole@mail.example.org")
        self.assertEqual(result["password"], "strong-password-42")

    def test_domain_primary_falls_back_to_system_email_domain(self) -> None:
        _write_users(
            self._tmp,
            "templated",
            {
                "sld": {
                    "username": "{{ DOMAIN_PRIMARY.split('.')[0] }}",
                    "accounts": [],
                }
            },
        )
        _reset_cache_for_tests()
        self.lookup._templar = _SplitTemplar(
            {"SYSTEM_EMAIL_DOMAIN": "mail.example.org"}
        )

        result = self.lookup.run(
            ["sld"],
            variables={"SYSTEM_EMAIL_DOMAIN": "mail.example.org"},
            roles_dir=str(self._tmp / "roles"),
        )[0]

        self.assertEqual(result["username"], "mail")

    def test_materializes_sld_without_templar_when_domain_primary_is_available(
        self,
    ) -> None:
        _write_users(
            self._tmp,
            "templated",
            {
                "sld": {
                    "username": "{{ DOMAIN_PRIMARY.split('.')[0] }}",
                    "accounts": [],
                }
            },
        )
        _reset_cache_for_tests()
        self.lookup._templar = None

        result = self.lookup.run(
            ["sld"],
            variables={"DOMAIN_PRIMARY": "portal.example.org"},
            roles_dir=str(self._tmp / "roles"),
        )[0]

        self.assertEqual(result["username"], "portal")

    def test_materializes_sld_from_role_domain_when_global_domain_is_missing(
        self,
    ) -> None:
        _write_users(
            self._tmp,
            "templated",
            {
                "sld": {
                    "username": "{{ DOMAIN_PRIMARY.split('.')[0] }}",
                    "accounts": [],
                }
            },
        )
        _reset_cache_for_tests()
        self.lookup._templar = None

        result = self.lookup.run(
            ["sld"],
            variables={"domain": "auth.infinito.test"},
            roles_dir=str(self._tmp / "roles"),
        )[0]

        self.assertEqual(result["username"], "infinito")

    def test_materializes_sld_from_env_backed_domain_primary(self) -> None:
        _write_users(
            self._tmp,
            "templated",
            {
                "sld": {
                    "username": "{{ DOMAIN_PRIMARY.split('.')[0] }}",
                    "accounts": [],
                }
            },
        )
        _reset_cache_for_tests()
        self.lookup._templar = None

        with patch.dict(os.environ, {"DOMAIN": "infinito.localhost"}, clear=False):
            result = self.lookup.run(
                ["sld"],
                variables={
                    "DOMAIN_PRIMARY": "{{ lookup('env', 'DOMAIN') | default('infinito.localhost', true) }}"
                },
                roles_dir=str(self._tmp / "roles"),
            )[0]

        self.assertEqual(result["username"], "infinito")

    def test_renders_templated_defaults_when_templar_returns_unchanged(self) -> None:
        _write_users(
            self._tmp,
            "templated",
            {
                "blackhole": {
                    "email": "blackhole@{{ SYSTEM_EMAIL_DOMAIN }}",
                    "password": "{{ 16 | strong_password }}",
                }
            },
        )
        _reset_cache_for_tests()
        self.lookup._templar = _NoopTemplar({"SYSTEM_EMAIL_DOMAIN": "mail.example.org"})

        result = self.lookup.run(
            ["blackhole"],
            variables={"SYSTEM_EMAIL_DOMAIN": "mail.example.org"},
            roles_dir=str(self._tmp / "roles"),
        )[0]

        self.assertEqual(result["email"], "blackhole@mail.example.org")
        self.assertNotRegex(result["password"], r"\{\{.*?\}\}")
        self.assertGreaterEqual(len(result["password"]), 12)

    def test_hydrates_tokens_from_dir_secrets_when_file_tokens_missing(self) -> None:
        secrets_dir = self._tmp / "var" / "lib" / "infinito" / "secrets"
        secrets_dir.mkdir(parents=True, exist_ok=True)
        (secrets_dir / FILE_TOKENS.name).write_text(
            dump_yaml_str(
                {
                    "users": {
                        "alice": {
                            "tokens": {
                                "web-app-mailu": "token-from-dir-secrets",
                            }
                        }
                    }
                }
            ),
            encoding="utf-8",
        )

        result = self.lookup.run(
            ["alice"],
            variables={"DIR_SECRETS": str(secrets_dir)},
            roles_dir=str(self._tmp / "roles"),
        )[0]

        self.assertEqual(result["tokens"]["web-app-mailu"], "token-from-dir-secrets")

    def test_hydrates_tokens_from_default_store_path_when_path_vars_missing(
        self,
    ) -> None:
        default_tokens = self._tmp / "default" / "tokens.yml"
        default_tokens.parent.mkdir(parents=True, exist_ok=True)
        default_tokens.write_text(
            dump_yaml_str(
                {
                    "users": {
                        "alice": {
                            "tokens": {
                                "web-app-mailu": "token-from-default-path",
                            }
                        }
                    }
                }
            ),
            encoding="utf-8",
        )
        previous_default = runtime_data_base.DEFAULT_TOKENS_FILE
        runtime_data_base.DEFAULT_TOKENS_FILE = default_tokens
        try:
            result = self.lookup.run(
                ["alice"],
                variables={},
                roles_dir=str(self._tmp / "roles"),
            )[0]
        finally:
            runtime_data_base.DEFAULT_TOKENS_FILE = previous_default

        self.assertEqual(result["tokens"]["web-app-mailu"], "token-from-default-path")


class _DummyTemplar:
    def __init__(self, variables: dict[str, str]) -> None:
        self.available_variables = dict(variables)

    def template(self, value, fail_on_undefined=False):
        if isinstance(value, dict):
            return {
                key: self.template(item, fail_on_undefined=fail_on_undefined)
                for key, item in value.items()
            }
        if isinstance(value, list):
            return [
                self.template(item, fail_on_undefined=fail_on_undefined)
                for item in value
            ]
        if not isinstance(value, str):
            return value

        rendered = value
        for key, replacement in self.available_variables.items():
            rendered = rendered.replace("{{ " + key + " }}", str(replacement))
        return rendered.replace("{{ 42 | strong_password }}", "strong-password-42")


class _NoopTemplar:
    def __init__(self, variables: dict[str, str]) -> None:
        self.available_variables = dict(variables)

    def template(self, value, fail_on_undefined=False):
        return value


class _SplitTemplar(_DummyTemplar):
    def template(self, value, fail_on_undefined=False):
        rendered = super().template(value, fail_on_undefined=fail_on_undefined)
        if not isinstance(rendered, str):
            return rendered

        domain_primary = self.available_variables.get("DOMAIN_PRIMARY")
        if domain_primary:
            rendered = rendered.replace(
                "{{ DOMAIN_PRIMARY.split('.')[0] }}",
                str(domain_primary).split(".")[0],
            )
        return rendered


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