"""Shared fixtures for API tests."""

from __future__ import annotations

import datetime
from collections.abc import Iterator
from typing import Any
from unittest.mock import MagicMock, patch

import jwt
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from fastapi.testclient import TestClient

from ilum.api.app import create_app
from ilum.api.deps import set_manager
from ilum.api.operations import OperationStore
from ilum.core.helm import HelmClient, HelmResult
from ilum.core.kubernetes import KubeClient, PodStatus
from ilum.core.modules import ModuleResolver
from ilum.core.release import ReleaseInfo, ReleaseManager, ReleasePlan

TEST_API_KEY = "test-key-for-unit-tests"


# ---------------------------------------------------------------------------
# RSA key pair for Bearer/JWT tests
# ---------------------------------------------------------------------------


@pytest.fixture(scope="session")
def rsa_key_pair() -> tuple[rsa.RSAPrivateKey, str]:
    """Generate an ephemeral RSA key pair. Returns (private_key, public_key_pem)."""
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    public_pem = (
        private_key.public_key()
        .public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo,
        )
        .decode()
    )
    return private_key, public_pem


@pytest.fixture()
def valid_jwt(rsa_key_pair: tuple[rsa.RSAPrivateKey, str]) -> str:
    """Create a valid JWT signed with the test RSA key."""
    private_key, _ = rsa_key_pair
    payload = {
        "sub": "test-user",
        "iss": "https://ilum.cloud",
        "exp": datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(hours=1),
        "iat": datetime.datetime.now(tz=datetime.UTC),
    }
    return jwt.encode(payload, private_key, algorithm="RS256")


@pytest.fixture()
def expired_jwt(rsa_key_pair: tuple[rsa.RSAPrivateKey, str]) -> str:
    """Create an expired JWT signed with the test RSA key."""
    private_key, _ = rsa_key_pair
    payload = {
        "sub": "test-user",
        "iss": "https://ilum.cloud",
        "exp": datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(hours=1),
        "iat": datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(hours=2),
    }
    return jwt.encode(payload, private_key, algorithm="RS256")


@pytest.fixture()
def wrong_issuer_jwt(rsa_key_pair: tuple[rsa.RSAPrivateKey, str]) -> str:
    """Create a JWT with the wrong issuer claim."""
    private_key, _ = rsa_key_pair
    payload = {
        "sub": "test-user",
        "iss": "https://evil.example.com",
        "exp": datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(hours=1),
        "iat": datetime.datetime.now(tz=datetime.UTC),
    }
    return jwt.encode(payload, private_key, algorithm="RS256")


@pytest.fixture()
def wrong_key_jwt() -> str:
    """Create a JWT signed with a DIFFERENT RSA key (signature won't verify)."""
    other_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    payload = {
        "sub": "test-user",
        "iss": "https://ilum.cloud",
        "exp": datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(hours=1),
        "iat": datetime.datetime.now(tz=datetime.UTC),
    }
    return jwt.encode(payload, other_key, algorithm="RS256")


@pytest.fixture()
def mock_api_helm() -> MagicMock:
    """Mocked HelmClient for API tests."""
    mock = MagicMock(spec=HelmClient)
    mock.namespace = "default"
    mock.kubecontext = ""
    mock._run.return_value = HelmResult(
        returncode=0, stdout="", stderr="", command=[], duration_seconds=0.0
    )
    return mock


@pytest.fixture()
def mock_api_k8s() -> MagicMock:
    """Mocked KubeClient for API tests."""
    mock = MagicMock(spec=KubeClient)
    mock.is_connected.return_value = True
    mock.get_server_version.return_value = "1.30.0"
    mock.get_pod_health.return_value = []
    # Job-related defaults
    mock.create_job.return_value = MagicMock()
    mock.list_jobs_by_label.return_value = []
    # Default get_job returns a running job (active=1, no terminal counters)
    _default_job = MagicMock()
    _default_job.status.conditions = []
    _default_job.status.active = 1
    _default_job.status.succeeded = None
    _default_job.status.failed = None
    mock.get_job.return_value = _default_job
    mock.get_job_pod_logs.return_value = ""
    mock.delete_job.return_value = None
    return mock


@pytest.fixture()
def mock_api_manager(mock_api_helm: MagicMock, mock_api_k8s: MagicMock) -> MagicMock:
    """Mocked ReleaseManager for API tests."""
    mock = MagicMock(spec=ReleaseManager)
    mock.helm = mock_api_helm
    mock.k8s = mock_api_k8s
    mock.resolver = ModuleResolver()
    mock.get_enabled_modules.return_value = []
    mock.fetch_live_values.return_value = {}
    mock.fetch_computed_values.return_value = {}
    mock.is_stuck.return_value = False
    mock.get_release_info.return_value = ReleaseInfo(
        name="ilum",
        namespace="default",
        status="deployed",
        chart="ilum",
        chart_version="6.7.0",
        revision=3,
        last_deployed="2024-01-01T00:00:00Z",
    )
    # Default ReleasePlan for plan_* methods
    _default_plan = ReleasePlan(
        action="upgrade",
        release="ilum",
        namespace="default",
        chart="ilum/ilum",
        set_flags=[],
    )
    mock.plan_enable.return_value = _default_plan
    mock.plan_disable.return_value = _default_plan
    mock.plan_upgrade.return_value = _default_plan
    return mock


@pytest.fixture()
def api_client(
    mock_api_manager: MagicMock, monkeypatch: pytest.MonkeyPatch
) -> Iterator[TestClient]:
    """TestClient with mocked dependencies, auth, and startup bypassed."""
    monkeypatch.setenv("ILUM_API_KEY", TEST_API_KEY)
    monkeypatch.setenv("ILUM_API_IMAGE", "ilum/api:test")
    monkeypatch.setenv("ILUM_SERVICE_ACCOUNT", "ilum-api")
    with patch("ilum.api.startup.auto_connect"):
        app = create_app()
        set_manager(mock_api_manager)  # type: ignore[arg-type]
        # Reset operation store singleton for each test
        import ilum.api.operations as ops_mod

        ops_mod._store = OperationStore()
        import ilum.api.chart_cache as cache_mod

        cache_mod._chart_defaults = None
        with TestClient(
            app,
            raise_server_exceptions=False,
            headers={"X-API-Key": TEST_API_KEY},
        ) as client:
            yield client

    # Reset singletons
    set_manager(None)  # type: ignore[arg-type]
    import ilum.api.operations as ops_mod2

    ops_mod2._store = None
    import ilum.api.chart_cache as cache_mod2

    cache_mod2._chart_defaults = None


@pytest.fixture()
def unauthed_client(
    mock_api_manager: MagicMock, monkeypatch: pytest.MonkeyPatch
) -> Iterator[TestClient]:
    """TestClient with NO auth headers — for testing auth rejection."""
    monkeypatch.setenv("ILUM_API_KEY", TEST_API_KEY)
    with patch("ilum.api.startup.auto_connect"):
        app = create_app()
        set_manager(mock_api_manager)  # type: ignore[arg-type]
        import ilum.api.operations as ops_mod

        ops_mod._store = OperationStore()
        import ilum.api.chart_cache as cache_mod

        cache_mod._chart_defaults = None
        with TestClient(app, raise_server_exceptions=False) as client:
            yield client

    set_manager(None)  # type: ignore[arg-type]
    import ilum.api.operations as ops_mod2

    ops_mod2._store = None
    import ilum.api.chart_cache as cache_mod2

    cache_mod2._chart_defaults = None


@pytest.fixture()
def sample_release_info() -> ReleaseInfo:
    return ReleaseInfo(
        name="ilum",
        namespace="default",
        status="deployed",
        chart="ilum",
        chart_version="6.7.0",
        revision=3,
        last_deployed="2024-01-01T00:00:00Z",
    )


@pytest.fixture()
def sample_pods() -> list[PodStatus]:
    return [
        PodStatus(
            name="ilum-core-0",
            namespace="default",
            phase="Running",
            ready=True,
            restart_count=0,
            containers=("core",),
        ),
        PodStatus(
            name="ilum-ui-abc123",
            namespace="default",
            phase="Running",
            ready=True,
            restart_count=1,
            containers=("nginx",),
        ),
    ]


@pytest.fixture()
def sample_values() -> dict[str, Any]:
    return {
        "ilum-core": {"enabled": True},
        "ilum-ui": {"enabled": True},
        "mongodb": {"enabled": True},
        "minio": {"enabled": True},
        "postgresql": {"enabled": True},
        "kafka": {"enabled": False},
        "ilum-jupyter": {"enabled": True},
    }
