"""Tests for complyform update command."""

from __future__ import annotations

import json
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch

if TYPE_CHECKING:
    from pathlib import Path

import pytest
from pydantic import ValidationError
from rich.console import Console

from complyform.cli.update_cmd import (
    ProfileChangeEntry,
    ProfileUpdateCheck,
    _check_cli_version,
    _check_profile_updates,
    _display_local_changelog,
)


class TestProfileChangeEntry:
    def test_valid(self) -> None:
        entry = ProfileChangeEntry(
            change_type="added",
            framework="soc2",
            description="Added SOC2 controls",
            breaking=False,
        )
        assert entry.change_type == "added"
        assert entry.framework == "soc2"
        assert entry.control_ids is None

    def test_with_control_ids(self) -> None:
        entry = ProfileChangeEntry(
            change_type="modified",
            framework="hipaa",
            control_ids=["164.312", "164.314"],
            description="Updated HIPAA controls",
            source_ref="NIST-SP-800-66r2",
            breaking=True,
        )
        assert entry.control_ids is not None
        assert len(entry.control_ids) == 2
        assert entry.breaking is True

    def test_invalid_change_type(self) -> None:
        with pytest.raises(ValidationError):
            ProfileChangeEntry(
                change_type="unknown",  # type: ignore[arg-type]
                framework="soc2",
                description="test",
                breaking=False,
            )


class TestProfileUpdateCheck:
    def test_no_update(self) -> None:
        check = ProfileUpdateCheck(
            current_bundle_version="2026.03.08",
            latest_bundle_version="2026.03.08",
            update_available=False,
        )
        assert not check.update_available
        assert check.download_url is None

    def test_update_available(self) -> None:
        check = ProfileUpdateCheck(
            current_bundle_version="2026.03.08",
            latest_bundle_version="2026.03.15",
            update_available=True,
            download_url="https://example.com/bundle.tar.gz",
            min_cli_version="0.1.0",
            changelog_entries=[
                ProfileChangeEntry(
                    change_type="added",
                    framework="nis2",
                    description="Added NIS2 framework",
                    breaking=False,
                )
            ],
        )
        assert check.update_available
        assert check.changelog_entries is not None
        assert len(check.changelog_entries) == 1


class TestCheckCliVersion:
    @patch("httpx.get")
    def test_newer_available(self, mock_get: MagicMock) -> None:
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {"info": {"version": "99.0.0"}}
        mock_get.return_value = mock_resp

        console = Console(stderr=True, quiet=True)
        result = _check_cli_version(console)
        assert result["update_available"] is True
        assert result["latest"] == "99.0.0"

    @patch("httpx.get")
    def test_up_to_date(self, mock_get: MagicMock) -> None:
        from complyform import __version__

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {"info": {"version": __version__}}
        mock_get.return_value = mock_resp

        console = Console(stderr=True, quiet=True)
        result = _check_cli_version(console)
        assert result["update_available"] is False

    @patch("httpx.get", side_effect=Exception("no network"))
    def test_network_failure(self, mock_get: MagicMock) -> None:
        console = Console(stderr=True, quiet=True)
        result = _check_cli_version(console)
        assert result["update_available"] is False


class TestCheckProfileUpdates:
    def test_community_tier_skip(self) -> None:
        console = Console(stderr=True, quiet=True)
        result = _check_profile_updates(
            console, tier="community", api_key=None, check_only=False
        )
        assert result["update_available"] is False

    @patch("complyform.cli.update_cmd.Path.home")
    def test_no_profiles_installed(self, mock_home: MagicMock, tmp_path: Path) -> None:
        mock_home.return_value = tmp_path
        console = Console(stderr=True, quiet=True)
        result = _check_profile_updates(
            console, tier="team", api_key="key123", check_only=False
        )
        assert result["current"] is None

    @patch("httpx.post")
    @patch("complyform.cli.update_cmd.Path.home")
    def test_up_to_date(
        self,
        mock_home: MagicMock,
        mock_post: MagicMock,
        tmp_path: Path,
    ) -> None:
        mock_home.return_value = tmp_path
        profiles_dir = tmp_path / ".complyform" / "profiles"
        profiles_dir.mkdir(parents=True)
        (profiles_dir / "VERSION").write_text("2026.03.08")

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {
            "current_bundle_version": "2026.03.08",
            "latest_bundle_version": "2026.03.08",
            "update_available": False,
        }
        mock_post.return_value = mock_resp

        console = Console(stderr=True, quiet=True)
        result = _check_profile_updates(
            console, tier="team", api_key="key123", check_only=False
        )
        assert result["update_available"] is False

    @patch("httpx.post")
    @patch("complyform.cli.update_cmd.Path.home")
    def test_min_cli_version_not_met(
        self,
        mock_home: MagicMock,
        mock_post: MagicMock,
        tmp_path: Path,
    ) -> None:
        mock_home.return_value = tmp_path
        profiles_dir = tmp_path / ".complyform" / "profiles"
        profiles_dir.mkdir(parents=True)
        (profiles_dir / "VERSION").write_text("2026.03.08")

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {
            "current_bundle_version": "2026.03.08",
            "latest_bundle_version": "2026.03.15",
            "update_available": True,
            "min_cli_version": "99.0.0",
        }
        mock_post.return_value = mock_resp

        console = Console(stderr=True, quiet=True)
        result = _check_profile_updates(
            console, tier="team", api_key="key123", check_only=False
        )
        assert result["update_available"] is True

    @patch("httpx.post")
    @patch("complyform.cli.update_cmd.Path.home")
    def test_check_only(
        self,
        mock_home: MagicMock,
        mock_post: MagicMock,
        tmp_path: Path,
    ) -> None:
        mock_home.return_value = tmp_path
        profiles_dir = tmp_path / ".complyform" / "profiles"
        profiles_dir.mkdir(parents=True)
        (profiles_dir / "VERSION").write_text("2026.03.08")

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {
            "current_bundle_version": "2026.03.08",
            "latest_bundle_version": "2026.03.15",
            "update_available": True,
            "download_url": "https://example.com/bundle.tar.gz",
            "min_cli_version": "0.1.0",
        }
        mock_post.return_value = mock_resp

        console = Console(stderr=True, quiet=True)
        result = _check_profile_updates(
            console, tier="team", api_key="key123", check_only=True
        )
        assert result["update_available"] is True


class TestDisplayLocalChangelog:
    @patch("complyform.cli.update_cmd.Path.home")
    def test_no_changelog_file(self, mock_home: MagicMock, tmp_path: Path) -> None:
        mock_home.return_value = tmp_path
        console = Console(stderr=True, quiet=True)
        # Should not raise
        _display_local_changelog(console)

    @patch("complyform.cli.update_cmd.Path.home")
    def test_with_changelog(self, mock_home: MagicMock, tmp_path: Path) -> None:
        mock_home.return_value = tmp_path
        profiles_dir = tmp_path / ".complyform" / "profiles"
        profiles_dir.mkdir(parents=True)
        changelog = {
            "entries": [
                {
                    "version": "2026.03.08",
                    "date": "2026-03-08",
                    "summary": "Initial release",
                    "breaking": False,
                    "changes": ["Added SOC2 framework"],
                }
            ]
        }
        (profiles_dir / "profile-changelog.json").write_text(json.dumps(changelog))

        console = Console(stderr=True, quiet=True)
        _display_local_changelog(console)


class TestAtomicProfileExtraction:
    @patch("os.replace")
    def test_atomic_replace_called(self, mock_replace: MagicMock) -> None:
        """Verify os.replace is used for atomic extraction."""
        # os.replace is used in _download_and_install_profiles
        # Just verify the function is importable and os.replace exists
        import os

        assert hasattr(os, "replace")


class TestCiEnvDetection:
    @patch.dict("os.environ", {"CI": "true"})
    def test_ci_detected(self) -> None:
        import os

        assert os.environ.get("CI") == "true"
