"""Tests for VCard application vocabulary."""

import os
import tempfile

import pytest

from mcard.model.vcard_vocabulary import (
    ApplicationResources,
    EnvResource,
    FileResource,
    NetworkResource,
    ObservabilityResource,
    ResourceCategory,
    StorageResource,
)


class TestEnvResource:
    """Test environment variable resource factory."""

    def test_create_env_resource(self):
        """Test creating an env resource."""
        ref = EnvResource.create("TEST_VAR")

        assert ref.uri == "env://TEST_VAR"
        assert ref.content_hash is not None
        assert ref.qos_metrics["category"] == ResourceCategory.ENVIRONMENT.value

    def test_env_resource_with_existing_var(self):
        """Test env resource for existing variable."""
        os.environ["TEST_EXISTING_VAR"] = "test_value"
        try:
            ref = EnvResource.create("TEST_EXISTING_VAR")
            assert ref.status == "verified"
        finally:
            del os.environ["TEST_EXISTING_VAR"]

    def test_env_resource_required_missing(self):
        """Test env resource for missing required variable."""
        # Ensure variable doesn't exist
        os.environ.pop("DEFINITELY_DOES_NOT_EXIST", None)

        ref = EnvResource.create("DEFINITELY_DOES_NOT_EXIST", required=True)
        assert ref.status == "invalid"

    def test_env_resource_optional_missing(self):
        """Test env resource for missing optional variable."""
        os.environ.pop("DEFINITELY_DOES_NOT_EXIST", None)

        ref = EnvResource.create("DEFINITELY_DOES_NOT_EXIST", required=False)
        assert ref.status == "pending"

    def test_env_resource_secret_flag(self):
        """Test env resource with secret flag."""
        ref = EnvResource.create("API_KEY", secret=True)
        assert ref.qos_metrics["secret"] is True

    def test_resolve_env_resource(self):
        """Test resolving env resource value."""
        os.environ["RESOLVE_TEST"] = "resolved_value"
        try:
            ref = EnvResource.create("RESOLVE_TEST")
            value = EnvResource.resolve(ref)
            assert value == "resolved_value"
        finally:
            del os.environ["RESOLVE_TEST"]


class TestFileResource:
    """Test filesystem resource factory."""

    def test_create_file_resource_existing(self):
        """Test creating a file resource for existing file."""
        with tempfile.NamedTemporaryFile(delete=False) as f:
            f.write(b"test content")
            temp_path = f.name

        try:
            ref = FileResource.create(temp_path)
            assert ref.uri.startswith("file://")
            assert ref.status == "verified"
            assert ref.qos_metrics["exists"] is True
        finally:
            os.unlink(temp_path)

    def test_create_file_resource_missing_required(self):
        """Test creating a file resource for missing required file."""
        ref = FileResource.create("/nonexistent/path/file.txt", required=True)
        assert ref.status == "invalid"

    def test_create_file_resource_missing_optional(self):
        """Test creating a file resource for missing optional file."""
        ref = FileResource.create("/nonexistent/path/file.txt", required=False)
        assert ref.status == "pending"

    def test_create_directory_resource(self):
        """Test creating a directory resource."""
        with tempfile.TemporaryDirectory() as temp_dir:
            ref = FileResource.create_directory(temp_dir)
            assert ref.uri.endswith("/")  # Directory marker
            assert ref.status == "verified"
            assert ref.qos_metrics["is_directory"] is True

    def test_verify_file_resource(self):
        """Test verifying file content hash."""
        with tempfile.NamedTemporaryFile(delete=False) as f:
            f.write(b"test content")
            temp_path = f.name

        try:
            ref = FileResource.create(temp_path)
            assert FileResource.verify(ref) is True

            # Modify file
            with open(temp_path, "wb") as f:
                f.write(b"modified content")

            # Verification should now fail
            assert FileResource.verify(ref) is False
        finally:
            os.unlink(temp_path)


class TestStorageResource:
    """Test storage resource factory."""

    def test_create_sqlite_resource(self):
        """Test creating a SQLite resource."""
        ref = StorageResource.create_sqlite("./data/test.db")

        assert ref.uri.startswith("sqlite://")
        assert ref.qos_metrics["engine"] == "sqlite"
        assert ref.qos_metrics["category"] == ResourceCategory.STORAGE.value

    def test_create_postgres_resource(self):
        """Test creating a PostgreSQL resource."""
        ref = StorageResource.create_postgres("localhost:5432/testdb")

        assert ref.uri == "postgres://localhost:5432/testdb"
        assert ref.qos_metrics["engine"] == "postgresql"

    def test_create_s3_resource(self):
        """Test creating an S3 resource."""
        ref = StorageResource.create_s3(
            "my-bucket", "path/to/object.json", region="eu-west-1"
        )

        assert ref.uri == "s3://my-bucket/path/to/object.json"
        assert ref.qos_metrics["bucket"] == "my-bucket"
        assert ref.qos_metrics["key"] == "path/to/object.json"
        assert ref.qos_metrics["region"] == "eu-west-1"


class TestNetworkResource:
    """Test network resource factory."""

    def test_create_api_resource(self):
        """Test creating an API resource."""
        ref = NetworkResource.create_api(
            "https://api.example.com/v1/users", method="POST", auth_env_var="API_TOKEN"
        )

        assert ref.uri == "https://api.example.com/v1/users"
        assert ref.qos_metrics["method"] == "POST"
        assert ref.qos_metrics["auth_env_var"] == "API_TOKEN"

    def test_create_webhook_resource(self):
        """Test creating a webhook resource."""
        ref = NetworkResource.create_webhook(
            "https://hooks.example.com/notify", secret_env_var="WEBHOOK_SECRET"
        )

        assert ref.uri == "https://hooks.example.com/notify"
        assert ref.qos_metrics["type"] == "webhook"


class TestApplicationResources:
    """Test the composite ApplicationResources class."""

    def test_add_multiple_resources(self):
        """Test adding multiple resource types."""
        # Set up env var
        os.environ["APP_TEST_VAR"] = "test"

        try:
            resources = ApplicationResources()
            resources.add_env("APP_TEST_VAR")
            resources.add_sqlite("./data/app.db", required=False)
            resources.add_api("https://api.example.com")

            all_refs = resources.get_all()
            assert len(all_refs) == 3
        finally:
            del os.environ["APP_TEST_VAR"]

    def test_filter_by_category(self):
        """Test filtering resources by category."""
        resources = ApplicationResources()
        resources.add_env("VAR1", required=False)
        resources.add_env("VAR2", required=False)
        resources.add_sqlite("./test.db", required=False)

        env_refs = resources.get_by_category(ResourceCategory.ENVIRONMENT)
        storage_refs = resources.get_by_category(ResourceCategory.STORAGE)

        assert len(env_refs) == 2
        assert len(storage_refs) == 1

    def test_validate_resources(self):
        """Test validation report."""
        os.environ["VALID_VAR"] = "exists"

        try:
            resources = ApplicationResources()
            resources.add_env("VALID_VAR")
            resources.add_env("MISSING_REQUIRED_VAR", required=True)
            resources.add_sqlite("./nonexistent.db", required=False)

            report = resources.validate()

            assert report["total"] == 3
            assert report["verified"] == 1
            assert report["invalid"] == 1
            assert report["valid"] is False
            assert "env://MISSING_REQUIRED_VAR" in report["invalid_resources"]
        finally:
            del os.environ["VALID_VAR"]


class TestObservabilityResource:
    """Test observability resource factory."""

    def test_create_grafana_resource(self):
        """Test creating a Grafana resource."""
        ref = ObservabilityResource.create_grafana(
            "http://localhost:3000", org_id="1", dashboard_uid="abc123"
        )

        assert ref.uri == "grafana://localhost:3000"
        assert ref.qos_metrics["type"] == "grafana"
        assert ref.qos_metrics["category"] == "observability"
        assert ref.qos_metrics["org_id"] == "1"
        assert ref.qos_metrics["dashboard_uid"] == "abc123"

    def test_create_prometheus_resource(self):
        """Test creating a Prometheus resource."""
        ref = ObservabilityResource.create_prometheus(
            "http://localhost:9090", job_name="mcard-service", scrape_interval=30
        )

        assert ref.uri == "prometheus://localhost:9090"
        assert ref.qos_metrics["type"] == "prometheus"
        assert ref.qos_metrics["job_name"] == "mcard-service"
        assert ref.qos_metrics["scrape_interval"] == 30

    def test_create_loki_resource(self):
        """Test creating a Loki resource."""
        ref = ObservabilityResource.create_loki(
            "http://localhost:3100",
            tenant_id="tenant1",
            labels={"service": "mcard", "env": "dev"},
        )

        assert ref.uri == "loki://localhost:3100"
        assert ref.qos_metrics["type"] == "loki"
        assert ref.qos_metrics["tenant_id"] == "tenant1"
        assert ref.qos_metrics["labels"]["service"] == "mcard"

    def test_create_tempo_resource(self):
        """Test creating a Tempo resource."""
        ref = ObservabilityResource.create_tempo(
            "http://localhost:4317", service_name="mcard-service", sampling_rate=0.5
        )

        assert ref.uri == "tempo://localhost:4317"
        assert ref.qos_metrics["type"] == "tempo"
        assert ref.qos_metrics["service_name"] == "mcard-service"
        assert ref.qos_metrics["sampling_rate"] == 0.5

    def test_create_faro_resource(self):
        """Test creating a Faro frontend observability resource."""
        ref = ObservabilityResource.create_faro(
            "https://faro.example.com/collect",
            app_name="mcard-ui",
            app_version="1.0.0",
            environment="production",
        )

        assert ref.uri == "faro://faro.example.com/collect"
        assert ref.qos_metrics["type"] == "faro"
        assert ref.qos_metrics["app_name"] == "mcard-ui"
        assert ref.qos_metrics["app_version"] == "1.0.0"
        assert ref.qos_metrics["environment"] == "production"
        assert "web_vitals" in ref.qos_metrics["features"]

    def test_create_otlp_resource(self):
        """Test creating an OTLP resource."""
        ref = ObservabilityResource.create_otlp(
            "http://localhost:4317", protocol="grpc", service_name="mcard-service"
        )

        assert ref.uri == "otlp://localhost:4317"
        assert ref.qos_metrics["type"] == "otlp"
        assert ref.qos_metrics["protocol"] == "grpc"
        assert "traces" in ref.qos_metrics["signals"]
        assert "metrics" in ref.qos_metrics["signals"]
        assert "logs" in ref.qos_metrics["signals"]

    def test_create_lgtm_stack(self):
        """Test creating a complete LGTM stack."""
        refs = ObservabilityResource.create_lgtm_stack(
            "http://localhost", "mcard-service", environment="dev"
        )

        assert len(refs) == 4

        types = [r.qos_metrics["type"] for r in refs]
        assert "grafana" in types
        assert "prometheus" in types
        assert "loki" in types
        assert "tempo" in types

        # Check custom ports
        grafana_ref = next(r for r in refs if r.qos_metrics["type"] == "grafana")
        assert "3000" in grafana_ref.qos_metrics["endpoint"]

    def test_lgtm_stack_custom_ports(self):
        """Test LGTM stack with custom ports."""
        refs = ObservabilityResource.create_lgtm_stack(
            "http://monitoring.local",
            "mcard-service",
            ports={"grafana": 8080, "prometheus": 9999},
        )

        grafana_ref = next(r for r in refs if r.qos_metrics["type"] == "grafana")
        prometheus_ref = next(r for r in refs if r.qos_metrics["type"] == "prometheus")

        assert "8080" in grafana_ref.qos_metrics["endpoint"]
        assert "9999" in prometheus_ref.qos_metrics["endpoint"]


class TestApplicationResourcesObservability:
    """Test ApplicationResources observability helpers."""

    def test_add_observability_resources(self):
        """Test adding observability resources via helpers."""
        resources = ApplicationResources()
        resources.add_grafana("http://localhost:3000")
        resources.add_prometheus("http://localhost:9090")
        resources.add_loki("http://localhost:3100")
        resources.add_tempo("http://localhost:4317")
        resources.add_faro("https://faro.example.com", "test-app")
        resources.add_otlp("http://localhost:4317")

        obs_refs = resources.get_observability_resources()
        assert len(obs_refs) == 6

    def test_add_lgtm_stack_helper(self):
        """Test adding complete LGTM stack via helper."""
        resources = ApplicationResources()
        resources.add_lgtm_stack("http://localhost", "mcard-service")

        obs_refs = resources.get_observability_resources()
        assert len(obs_refs) == 4

        types = [r.qos_metrics["type"] for r in obs_refs]
        assert "grafana" in types
        assert "prometheus" in types
        assert "loki" in types
        assert "tempo" in types


class TestDotenvParsing:
    """Test .env file parsing."""

    def test_create_from_dotenv(self):
        """Test creating resources from .env file."""
        # Create temp .env file
        with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as f:
            f.write("DATABASE_URL=postgres://localhost/db\n")
            f.write("API_KEY=secret123\n")  # Should be marked as secret
            f.write("# This is a comment\n")
            f.write("DEBUG=true\n")
            temp_path = f.name

        try:
            refs = EnvResource.create_from_dotenv(temp_path)

            assert len(refs) == 3

            # Find the API_KEY ref
            api_key_ref = next(r for r in refs if "API_KEY" in r.uri)
            assert api_key_ref.qos_metrics["secret"] is True

            # DEBUG should not be marked as secret
            debug_ref = next(r for r in refs if "DEBUG" in r.uri)
            assert debug_ref.qos_metrics["secret"] is False
        finally:
            os.unlink(temp_path)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
