"""Tests for ``ilum auth`` sub-commands."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest
from typer.testing import CliRunner

from ilum.cli.main import app
from ilum.core.helm import HelmResult
from ilum.core.release import ReleaseManager, ReleasePlan
from ilum.core.safety import DriftResult, ValuesDiff


@pytest.fixture()
def runner() -> CliRunner:
    return CliRunner()


@pytest.fixture()
def mock_mgr() -> MagicMock:
    mock = MagicMock(spec=ReleaseManager)
    mock.helm = MagicMock()
    mock.helm.namespace = "default"
    mock.release_exists.return_value = True
    mock.preview_command.return_value = ["helm", "upgrade", "ilum", "ilum/ilum"]
    return mock


def _make_plan(
    action: str = "upgrade",
    set_flags: list[str] | None = None,
    has_diff: bool = True,
) -> ReleasePlan:
    diff = ValuesDiff(
        added={"ilum-core.security.type": "oauth2"} if has_diff else {},
        changed={},
        removed={},
    )
    return ReleasePlan(
        action=action,
        release="ilum",
        namespace="default",
        chart="ilum/ilum",
        set_flags=set_flags or [],
        effective_diff=diff,
        drift=DriftResult(has_drift=False, snapshot_exists=False, diff=None, live_values={}),
    )


# ---------------------------------------------------------------------------
# ilum auth --help
# ---------------------------------------------------------------------------


class TestAuthHelp:
    def test_auth_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["auth", "--help"])
        assert result.exit_code == 0
        assert "enable" in result.output
        assert "disable" in result.output
        assert "status" in result.output
        assert "list" in result.output
        assert "setup" in result.output

    def test_auth_enable_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["auth", "enable", "--help"])
        assert result.exit_code == 0
        assert "--client-id" in result.output
        assert "--client-secret" in result.output


# ---------------------------------------------------------------------------
# ilum auth status
# ---------------------------------------------------------------------------


class TestAuthStatus:
    @patch("ilum.cli.auth_cmd._build_manager")
    def test_internal_mode(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"security": {"type": "internal"}},
        }
        mock_build.return_value = mock_mgr

        result = runner.invoke(app, ["auth", "status"])
        assert result.exit_code == 0
        assert "internal" in result.output

    @patch("ilum.cli.auth_cmd._build_manager")
    def test_hydra_mode(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        mock_mgr.fetch_computed_values.return_value = {
            "global": {
                "security": {
                    "hydra": {
                        "enabled": True,
                        "uiDomain": "ilum.example.com",
                        "uiProtocol": "https",
                        "clientId": "my-client",
                    }
                }
            },
        }
        mock_build.return_value = mock_mgr

        result = runner.invoke(app, ["auth", "status"])
        assert result.exit_code == 0
        assert "hydra" in result.output
        assert "ilum.example.com" in result.output

    @patch("ilum.cli.auth_cmd._build_manager")
    def test_json_output(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"security": {"type": "internal"}},
        }
        mock_build.return_value = mock_mgr

        result = runner.invoke(app, ["--output", "json", "auth", "status"])
        assert result.exit_code == 0
        assert "internal" in result.output


# ---------------------------------------------------------------------------
# ilum auth list
# ---------------------------------------------------------------------------


class TestAuthList:
    def test_lists_all_providers(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["auth", "list"])
        assert result.exit_code == 0
        assert "google" in result.output
        assert "hydra" in result.output
        assert "keycloak" in result.output
        assert "azure-ad" in result.output
        assert "aws-cognito" in result.output
        assert "okta" in result.output
        assert "oidc" in result.output
        assert "github" in result.output
        assert "gitlab" in result.output

    def test_json_output(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["--output", "json", "auth", "list"])
        assert result.exit_code == 0
        assert "google" in result.output
        assert "hydra" in result.output


# ---------------------------------------------------------------------------
# ilum auth enable
# ---------------------------------------------------------------------------


class TestAuthEnable:
    def test_unknown_provider_error(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["auth", "enable", "nonexistent"])
        assert result.exit_code == 1
        assert "Unknown auth provider" in result.output

    def test_missing_required_param_error(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["auth", "enable", "google"])
        assert result.exit_code == 1
        assert "Missing required parameter" in result.output

    @patch("ilum.cli.auth_cmd._build_manager")
    def test_google_dry_run(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        plan = _make_plan()
        mock_mgr.plan_upgrade.return_value = plan
        mock_build.return_value = mock_mgr

        result = runner.invoke(
            app,
            [
                "auth",
                "enable",
                "google",
                "--client-id",
                "goog-id",
                "--client-secret",
                "goog-secret",
                "--dry-run",
            ],
        )
        assert result.exit_code == 0
        assert "Dry-run" in result.output
        mock_mgr.execute.assert_not_called()

    @patch("ilum.cli.auth_cmd._build_manager")
    def test_hydra_enable(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        plan = _make_plan()
        mock_mgr.plan_upgrade.return_value = plan
        mock_mgr.execute.return_value = HelmResult(returncode=0, stdout="", stderr="", command=[])
        mock_build.return_value = mock_mgr

        result = runner.invoke(
            app,
            [
                "auth",
                "enable",
                "hydra",
                "--domain",
                "ilum.example.com",
                "--client-id",
                "my-client",
                "--client-secret",
                "my-secret",
                "--yes",
            ],
        )
        assert result.exit_code == 0
        flags = mock_mgr.plan_upgrade.call_args.kwargs.get(
            "set_flags"
        ) or mock_mgr.plan_upgrade.call_args[1].get("set_flags", [])
        assert "global.security.hydra.enabled=true" in flags
        assert "global.security.hydra.uiDomain=ilum.example.com" in flags

    @patch("ilum.cli.auth_cmd._build_manager")
    def test_keycloak_enable(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        plan = _make_plan()
        mock_mgr.plan_upgrade.return_value = plan
        mock_mgr.execute.return_value = HelmResult(returncode=0, stdout="", stderr="", command=[])
        mock_build.return_value = mock_mgr

        result = runner.invoke(
            app,
            [
                "auth",
                "enable",
                "keycloak",
                "--client-id",
                "kc-id",
                "--client-secret",
                "kc-secret",
                "--url",
                "https://kc.example.com",
                "--realm",
                "myrealm",
                "--yes",
            ],
        )
        assert result.exit_code == 0
        flags = mock_mgr.plan_upgrade.call_args.kwargs.get(
            "set_flags"
        ) or mock_mgr.plan_upgrade.call_args[1].get("set_flags", [])
        assert "ilum-core.security.type=oauth2" in flags
        assert "global.security.oauth2.issuerUri=https://kc.example.com/realms/myrealm" in flags

    @patch("ilum.cli.auth_cmd._build_manager")
    def test_secret_masking_in_output(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        plan = _make_plan()
        mock_mgr.plan_upgrade.return_value = plan
        mock_mgr.execute.return_value = HelmResult(returncode=0, stdout="", stderr="", command=[])
        mock_build.return_value = mock_mgr

        result = runner.invoke(
            app,
            [
                "auth",
                "enable",
                "google",
                "--client-id",
                "my-google-client-id",
                "--client-secret",
                "super-secret-value",
                "--yes",
            ],
        )
        assert result.exit_code == 0
        # Client secret should be masked
        assert "super-secret-value" not in result.output
        assert "supe****" in result.output


# ---------------------------------------------------------------------------
# ilum auth disable
# ---------------------------------------------------------------------------


class TestAuthDisable:
    @patch("ilum.cli.auth_cmd._build_manager")
    def test_dry_run(self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner) -> None:
        plan = _make_plan()
        mock_mgr.plan_upgrade.return_value = plan
        mock_build.return_value = mock_mgr

        result = runner.invoke(app, ["auth", "disable", "--dry-run"])
        assert result.exit_code == 0
        assert "Dry-run" in result.output
        mock_mgr.execute.assert_not_called()

    @patch("ilum.cli.auth_cmd._build_manager")
    def test_successful_disable(
        self, mock_build: MagicMock, mock_mgr: MagicMock, runner: CliRunner
    ) -> None:
        plan = _make_plan()
        mock_mgr.plan_upgrade.return_value = plan
        mock_mgr.execute.return_value = HelmResult(returncode=0, stdout="", stderr="", command=[])
        mock_build.return_value = mock_mgr

        result = runner.invoke(app, ["auth", "disable", "--yes"])
        assert result.exit_code == 0
        flags = mock_mgr.plan_upgrade.call_args.kwargs.get(
            "set_flags"
        ) or mock_mgr.plan_upgrade.call_args[1].get("set_flags", [])
        assert "ilum-core.security.type=internal" in flags
        assert "global.security.hydra.enabled=false" in flags
