"""
Tests for BLOOM LIMS Pydantic Schemas

These tests verify that the Pydantic schemas work correctly for
validation, serialization, and deserialization.
"""

from datetime import datetime

import pytest


@pytest.fixture(autouse=True)
def _default_meridian_runtime(monkeypatch):
    monkeypatch.setenv("MERIDIAN_ENVIRONMENT", "production")
    monkeypatch.setenv("MERIDIAN_SANDBOX_PREFIX", "")


class TestBaseSchemas:
    """Tests for base schema functionality."""

    def test_pagination_params(self):
        """Test PaginationParams schema."""
        from bloom_lims.schemas import PaginationParams

        params = PaginationParams(page=1, page_size=50)
        assert params.page == 1
        assert params.page_size == 50

        # Test defaults
        params_default = PaginationParams()
        assert params_default.page == 1
        assert params_default.page_size == 50

    def test_euid_validation(self, monkeypatch):
        """Test EUID validation function.

        BLOOM uses TapDB/Meridian EUIDs:
        - Production: PREFIX-BODYCHECK (e.g. BCN-19, GT-13)
        - BODY is Crockford Base32 (no leading zeros)
        - CHECK is MOD32 checksum
        """
        from daylily_tapdb.euid import format_euid

        from bloom_lims.schemas import validate_euid

        cx1 = format_euid("BCN", 1)
        cx123 = format_euid("BCN", 123)
        wx1000 = format_euid("BWF", 1000)

        # Valid EUIDs (TapDB/Meridian)
        assert validate_euid(cx1) == cx1
        assert validate_euid(cx123) == cx123
        assert (
            validate_euid(f"  {wx1000.lower()}  ") == wx1000
        )  # Should uppercase and strip

        # Invalid EUIDs
        with pytest.raises(ValueError):
            validate_euid("")
        with pytest.raises(ValueError):
            validate_euid("   ")
        with pytest.raises(ValueError):
            validate_euid("CX1")  # Missing dash+checksum segment
        with pytest.raises(ValueError):
            validate_euid("BCN-01")  # Leading zeros not allowed in BODY

        # Bad checksum
        bad_checksum = cx1[:-1] + ("0" if cx1[-1] != "0" else "1")
        with pytest.raises(ValueError):
            validate_euid(bad_checksum)

        # Sandbox EUIDs are not accepted by Bloom's public schema validators
        sandbox = format_euid("BCN", 1, sandbox="X")
        with pytest.raises(ValueError):
            validate_euid(sandbox)

    def test_euid_validation_accepts_only_configured_sandbox_prefix(self, monkeypatch):
        monkeypatch.setenv("MERIDIAN_ENVIRONMENT", "sandbox")
        monkeypatch.setenv("MERIDIAN_SANDBOX_PREFIX", "S")

        from daylily_tapdb.euid import format_euid

        from bloom_lims.schemas import validate_euid

        sandbox = format_euid("BCN", 1, sandbox="S")
        wrong_prefix = format_euid("BCN", 1, sandbox="T")
        production = format_euid("BCN", 1)

        assert validate_euid(sandbox) == sandbox
        with pytest.raises(ValueError):
            validate_euid(wrong_prefix)
        with pytest.raises(ValueError):
            validate_euid(production)


class TestObjectSchemas:
    """Tests for object schemas."""

    def test_object_create_schema(self):
        """Test ObjectCreateSchema validation."""
        from bloom_lims.schemas import ObjectCreateSchema

        data = ObjectCreateSchema(
            name="Test Object",
            type="sample",
            subtype="blood",
        )
        assert data.name == "Test Object"
        assert data.type == "sample"
        assert data.subtype == "blood"

    def test_object_create_with_json_addl(self):
        """Test ObjectCreateSchema with json_addl."""
        from bloom_lims.schemas import ObjectCreateSchema

        data = ObjectCreateSchema(
            name="Test Object",
            type="sample",
            json_addl={"properties": {"key": "value"}},
        )
        assert data.json_addl == {"properties": {"key": "value"}}


class TestContainerSchemas:
    """Tests for container schemas."""

    def test_container_create_schema(self):
        """Test ContainerCreateSchema validation."""
        from daylily_tapdb.euid import format_euid

        from bloom_lims.schemas import ContainerCreateSchema

        data = ContainerCreateSchema(
            name="Test Plate",
            container_type="plate",
            subtype="96-well",
            template_euid=format_euid("GT", 1),
        )
        assert data.name == "Test Plate"
        assert data.container_type == "plate"

    def test_container_layout_schema(self):
        """Test ContainerLayoutSchema."""
        from bloom_lims.schemas import ContainerLayoutSchema

        layout = ContainerLayoutSchema(
            rows=8,
            columns=12,
            layout_type="grid",
        )
        assert layout.rows == 8
        assert layout.columns == 12


class TestContentSchemas:
    """Tests for content schemas."""

    def test_sample_create_schema(self):
        """Test SampleCreateSchema validation."""
        from daylily_tapdb.euid import format_euid

        from bloom_lims.schemas import SampleCreateSchema

        data = SampleCreateSchema(
            name="Test Sample",
            sample_type="blood",
            template_euid=format_euid("GT", 2),
        )
        assert data.name == "Test Sample"
        assert data.sample_type == "blood"

    def test_reagent_create_schema(self):
        """Test ReagentCreateSchema validation."""
        from daylily_tapdb.euid import format_euid

        from bloom_lims.schemas import ReagentCreateSchema

        data = ReagentCreateSchema(
            name="Test Reagent",
            reagent_type="buffer",
            template_euid=format_euid("GT", 3),
            lot_number="LOT001",
        )
        assert data.lot_number == "LOT001"


class TestWorkflowSchemas:
    """Tests for workflow schemas."""

    def test_workflow_create_schema(self):
        """Test WorkflowCreateSchema validation."""
        from daylily_tapdb.euid import format_euid

        from bloom_lims.schemas import WorkflowCreateSchema

        data = WorkflowCreateSchema(
            name="Test Workflow",
            workflow_type="sequencing",
            template_euid=format_euid("GT", 4),
        )
        assert data.name == "Test Workflow"

    def test_workflow_step_schema(self):
        """Test WorkflowStepSchema validation."""
        from bloom_lims.schemas import WorkflowStepSchema

        step = WorkflowStepSchema(
            name="Step 1",
            step_number=1,
            step_type="extraction",
        )
        assert step.step_number == 1


class TestEquipmentSchemas:
    """Tests for equipment schemas."""

    def test_equipment_create_schema(self):
        """Test EquipmentCreateSchema validation."""
        from bloom_lims.schemas import EquipmentCreateSchema

        data = EquipmentCreateSchema(
            name="Test Sequencer",
            equipment_type="sequencer",
            serial_number="SN12345",
            manufacturer="Illumina",
        )
        assert data.serial_number == "SN12345"

    def test_maintenance_record_schema(self):
        """Test MaintenanceRecordSchema validation."""
        from bloom_lims.schemas import MaintenanceRecordSchema

        record = MaintenanceRecordSchema(
            maintenance_type="preventive",
            performed_date=datetime.now(),
            performed_by="Tech1",
        )
        assert record.maintenance_type == "preventive"


class TestPublicResponseSchemas:
    """Tests for public response schema contracts."""

    def test_public_response_schemas_do_not_expose_uuid_fields(self):
        from bloom_lims.schemas.actions import ActionResponseSchema
        from bloom_lims.schemas.containers import (
            ContainerPositionSchema,
            ContainerResponseSchema,
        )
        from bloom_lims.schemas.content import ContentResponseSchema
        from bloom_lims.schemas.equipment import EquipmentResponseSchema
        from bloom_lims.schemas.files import FileResponseSchema, FileSetResponseSchema
        from bloom_lims.schemas.objects import ObjectResponseSchema
        from bloom_lims.schemas.workflows import (
            WorkflowResponseSchema,
            WorkflowStepResponseSchema,
        )

        schemas = [
            ActionResponseSchema,
            ContainerPositionSchema,
            ContainerResponseSchema,
            ContentResponseSchema,
            EquipmentResponseSchema,
            FileResponseSchema,
            FileSetResponseSchema,
            ObjectResponseSchema,
            WorkflowResponseSchema,
            WorkflowStepResponseSchema,
        ]

        for schema in schemas:
            assert "uuid" not in schema.model_fields


class TestDomainUtils:
    """Tests for bloom_lims.domain.utils module."""

    def test_get_clean_timestamp(self):
        """Test clean timestamp generation."""
        from bloom_lims.domain.utils import get_clean_timestamp

        ts = get_clean_timestamp()
        assert isinstance(ts, str)
        # Format should be YYYY-MM-DD_HH-MM-SS
        assert len(ts) == 19
        assert ts[4] == "-"
        assert ts[7] == "-"
        assert ts[10] == "_"

    def test_generate_random_string_default_length(self):
        """Test random string generation with default length."""
        from bloom_lims.domain.utils import generate_random_string

        s = generate_random_string()
        assert isinstance(s, str)
        assert len(s) == 10
        # Should only contain alphanumeric characters
        assert s.isalnum()

    def test_generate_random_string_custom_length(self):
        """Test random string generation with custom length."""
        from bloom_lims.domain.utils import generate_random_string

        s = generate_random_string(20)
        assert len(s) == 20

        s2 = generate_random_string(5)
        assert len(s2) == 5

    def test_get_datetime_string(self):
        """Test datetime string generation."""
        from bloom_lims.domain.utils import get_datetime_string

        dt = get_datetime_string()
        assert isinstance(dt, str)
        # Should contain date, time, and timezone info
        assert "-" in dt  # Date separators
        assert ":" in dt  # Time separators

    def test_update_recursive_simple(self):
        """Test recursive dictionary update with simple values."""
        from bloom_lims.domain.utils import update_recursive

        orig = {"a": 1, "b": 2}
        update = {"b": 3, "c": 4}
        update_recursive(orig, update)
        assert orig == {"a": 1, "b": 3, "c": 4}

    def test_update_recursive_nested(self):
        """Test recursive dictionary update with nested dicts."""
        from bloom_lims.domain.utils import update_recursive

        orig = {"a": {"x": 1, "y": 2}, "b": 3}
        update = {"a": {"y": 20, "z": 30}}
        update_recursive(orig, update)
        assert orig == {"a": {"x": 1, "y": 20, "z": 30}, "b": 3}

    def test_unique_non_empty_strings(self):
        """Test unique non-empty string filtering."""
        from bloom_lims.domain.utils import unique_non_empty_strings

        # Should remove duplicates and empty strings
        result = unique_non_empty_strings(["a", "b", "", "a", "c", ""])
        assert set(result) == {"a", "b", "c"}

    def test_unique_non_empty_strings_all_empty(self):
        """Test unique non-empty strings with all empty."""
        from bloom_lims.domain.utils import unique_non_empty_strings

        result = unique_non_empty_strings(["", "", ""])
        assert result == []


class TestBloomExceptions:
    """Tests for bloom_lims.exceptions module.

    Note: Exception tests are skipped because exceptions log on creation
    and the logger 'extra' dict contains 'message' which conflicts with
    Python's built-in LogRecord 'message' attribute.
    """

    @pytest.mark.skip(
        reason="Logger extra contains 'message' key that conflicts with LogRecord"
    )
    def test_bloom_error_basic(self):
        """Test basic BloomError creation."""
        from bloom_lims.exceptions import BloomError

        error = BloomError("Test error message")
        assert error.message == "Test error message"

    @pytest.mark.skip(
        reason="Logger extra contains 'message' key that conflicts with LogRecord"
    )
    def test_validation_error(self):
        """Test ValidationError subclass."""
        from bloom_lims.exceptions import ValidationError

        error = ValidationError("Invalid input", field="email")
        assert error.http_status == 400


class TestExceptionImports:
    """Test that exception classes can be imported."""

    def test_import_bloom_error(self):
        """Test BloomError can be imported."""
        from bloom_lims.exceptions import BloomError

        assert BloomError is not None

    def test_import_validation_error(self):
        """Test ValidationError can be imported."""
        from bloom_lims.exceptions import ValidationError

        assert ValidationError is not None

    def test_import_not_found_error(self):
        """Test NotFoundError can be imported."""
        from bloom_lims.exceptions import NotFoundError

        assert NotFoundError is not None

    def test_import_database_error(self):
        """Test DatabaseError can be imported."""
        from bloom_lims.exceptions import DatabaseError

        assert DatabaseError is not None

    def test_import_authentication_error(self):
        """Test AuthenticationError can be imported."""
        from bloom_lims.exceptions import AuthenticationError

        assert AuthenticationError is not None

    def test_import_authorization_error(self):
        """Test AuthorizationError can be imported."""
        from bloom_lims.exceptions import AuthorizationError

        assert AuthorizationError is not None

    def test_import_configuration_error(self):
        """Test ConfigurationError can be imported."""
        from bloom_lims.exceptions import ConfigurationError

        assert ConfigurationError is not None

    def test_import_external_service_error(self):
        """Test ExternalServiceError can be imported."""
        from bloom_lims.exceptions import ExternalServiceError

        assert ExternalServiceError is not None


class TestDomainWorkflows:
    """Tests for workflow domain module imports and utilities."""

    def test_import_bloom_workflow(self):
        """Test BloomWorkflow import."""
        from bloom_lims.domain.workflows import BloomWorkflow

        assert BloomWorkflow is not None

    def test_import_bloom_workflow_step(self):
        """Test BloomWorkflowStep import."""
        from bloom_lims.domain.workflows import BloomWorkflowStep

        assert BloomWorkflowStep is not None

    def test_workflow_inherits_from_bloom_obj(self):
        """Test BloomWorkflow inherits from BloomObj."""
        from bloom_lims.domain.base import BloomObj
        from bloom_lims.domain.workflows import BloomWorkflow

        assert issubclass(BloomWorkflow, BloomObj)


class TestDomainFiles:
    """Tests for files domain module imports."""

    def test_import_bloom_file(self):
        """Test BloomFile import."""
        from bloom_lims.domain.files import BloomFile

        assert BloomFile is not None

    def test_import_bloom_file_set(self):
        """Test BloomFileSet import."""
        from bloom_lims.domain.files import BloomFileSet

        assert BloomFileSet is not None

    def test_file_inherits_from_bloom_obj(self):
        """Test BloomFile inherits from BloomObj."""
        from bloom_lims.domain.base import BloomObj
        from bloom_lims.domain.files import BloomFile

        assert issubclass(BloomFile, BloomObj)


class TestDomainContainers:
    """Tests for containers domain module imports."""

    def test_import_bloom_container(self):
        """Test BloomContainer import."""
        from bloom_lims.domain.containers import BloomContainer

        assert BloomContainer is not None


class TestDomainContent:
    """Tests for content domain module imports."""

    def test_import_bloom_content(self):
        """Test BloomContent import."""
        from bloom_lims.domain.content import BloomContent

        assert BloomContent is not None


class TestDomainEquipment:
    """Tests for equipment domain module imports."""

    def test_import_bloom_equipment(self):
        """Test BloomEquipment import."""
        from bloom_lims.domain.equipment import BloomEquipment

        assert BloomEquipment is not None


class TestCoreModuleImports:
    """Tests for core module imports."""

    def test_import_lru_cache(self):
        """Test LRUCache import."""
        from bloom_lims.core.cache import LRUCache

        assert LRUCache is not None

    def test_import_cache_entry(self):
        """Test CacheEntry import."""
        from bloom_lims.core.cache import CacheEntry

        assert CacheEntry is not None

    def test_import_cache_stats(self):
        """Test CacheStats import."""
        from bloom_lims.core.cache import CacheStats

        assert CacheStats is not None

    def test_import_template_validator(self):
        """Test TemplateValidator import."""
        from bloom_lims.core.template_validation import TemplateValidator

        assert TemplateValidator is not None

    def test_import_batch_processor(self):
        """Test BatchProcessor import."""
        from bloom_lims.core.batch_operations import BatchProcessor

        assert BatchProcessor is not None

    def test_import_async_operations(self):
        """Test async operations imports."""
        from bloom_lims.core.async_operations import BackgroundTaskManager, TaskStatus

        assert BackgroundTaskManager is not None
        assert TaskStatus is not None

    def test_import_task_result(self):
        """Test TaskResult import."""
        from bloom_lims.core.async_operations import TaskResult

        assert TaskResult is not None


class TestHealthModuleImports:
    """Tests for health module imports."""

    def test_import_health_router(self):
        """Test health_router import."""
        from bloom_lims.health import health_router

        assert health_router is not None

    def test_import_component_health(self):
        """Test ComponentHealth import."""
        from bloom_lims.health import ComponentHealth

        assert ComponentHealth is not None

    def test_import_health_response(self):
        """Test HealthResponse import."""
        from bloom_lims.health import HealthResponse

        assert HealthResponse is not None

    def test_import_liveness_response(self):
        """Test LivenessResponse import."""
        from bloom_lims.health import LivenessResponse

        assert LivenessResponse is not None

    def test_import_readiness_response(self):
        """Test ReadinessResponse import."""
        from bloom_lims.health import ReadinessResponse

        assert ReadinessResponse is not None

    def test_component_health_model(self):
        """Test ComponentHealth model creation."""
        from bloom_lims.health import ComponentHealth

        component = ComponentHealth(
            name="test", status="healthy", latency_ms=10.5, message="Test component"
        )
        assert component.name == "test"
        assert component.status == "healthy"
        assert component.latency_ms == 10.5

    def test_liveness_response_model(self):
        """Test LivenessResponse model creation."""
        from datetime import datetime

        from bloom_lims.health import LivenessResponse

        response = LivenessResponse(status="ok", timestamp=datetime.utcnow())
        assert response.status == "ok"
        assert response.timestamp is not None

    def test_get_system_info(self):
        """Test get_system_info function."""
        from bloom_lims.health import get_system_info

        info = get_system_info()
        assert isinstance(info, dict)
        # Should have system resource info
        if info:  # May be empty if psutil fails
            expected_keys = ["cpu_percent", "memory_percent", "python_version"]
            for key in expected_keys:
                if key in info:
                    assert info[key] is not None


class TestDomainBaseImports:
    """Tests for domain base module imports."""

    def test_import_bloom_obj(self):
        """Test BloomObj import."""
        from bloom_lims.domain.base import BloomObj

        assert BloomObj is not None

    def test_import_db_class(self):
        """Test BLOOMdb3 import."""
        from bloom_lims.db import BLOOMdb3

        assert BLOOMdb3 is not None


class TestConfigModuleTests:
    """Tests for config module."""

    def test_import_settings(self):
        """Test get_settings import."""
        from bloom_lims.config import get_settings

        assert get_settings is not None

    def test_get_settings_returns_settings(self):
        """Test get_settings returns a settings object."""
        from bloom_lims.config import get_settings

        settings = get_settings()
        assert settings is not None
        assert hasattr(settings, "environment")

    def test_settings_has_api_config(self):
        """Test settings has API config."""
        from bloom_lims.config import get_settings

        settings = get_settings()
        assert hasattr(settings, "api")
        assert hasattr(settings.api, "version")

    def test_settings_has_auth_config(self):
        """Test settings has auth config."""
        from bloom_lims.config import get_settings

        settings = get_settings()
        assert hasattr(settings, "auth")


class TestAPIVersioningImports:
    """Tests for API versioning imports."""

    def test_import_versioning(self):
        """Test versioning module import."""
        from bloom_lims.api import versioning

        assert versioning is not None


class TestRateLimitingImports:
    """Tests for rate limiting imports."""

    def test_import_rate_limiting(self):
        """Test rate limiting module import."""
        from bloom_lims.api import rate_limiting

        assert rate_limiting is not None

    def test_import_rate_limit_middleware(self):
        """Test RateLimitMiddleware import."""
        from bloom_lims.api.rate_limiting import RateLimitMiddleware

        assert RateLimitMiddleware is not None


class TestDomainBaseImportsExtended:
    """Extended tests for domain base module."""

    def test_import_bloom_obj_class(self):
        """Test BloomObj class import."""
        from bloom_lims.domain.base import BloomObj

        assert BloomObj is not None

    def test_import_bloomdb3_class(self):
        """Test BLOOMdb3 class import."""
        from bloom_lims.db import BLOOMdb3

        assert BLOOMdb3 is not None

    def test_bloom_obj_has_get_by_euid(self):
        """Test BloomObj has get_by_euid method."""
        from bloom_lims.domain.base import BloomObj

        assert hasattr(BloomObj, "get_by_euid")

    def test_bloom_obj_does_not_have_uuid_helpers(self):
        """Test BloomObj no longer exposes UUID helper methods."""
        from bloom_lims.domain.base import BloomObj

        assert not hasattr(BloomObj, "get")
        assert not hasattr(BloomObj, "delete_by_uuid")
        assert not hasattr(BloomObj, "create_instances_from_uuid")

    def test_bloom_obj_has_create_instance(self):
        """Test BloomObj has create_instance method."""
        from bloom_lims.domain.base import BloomObj

        assert hasattr(BloomObj, "create_instance")


class TestCoreExceptionsImports:
    """Tests for core exceptions module."""

    def test_import_bloom_error(self):
        """Test BloomError import."""
        from bloom_lims.core.exceptions import BloomError

        assert BloomError is not None

    def test_import_bloom_not_found_error(self):
        """Test BloomNotFoundError import."""
        from bloom_lims.core.exceptions import BloomNotFoundError

        assert BloomNotFoundError is not None

    def test_import_bloom_database_error(self):
        """Test BloomDatabaseError import."""
        from bloom_lims.core.exceptions import BloomDatabaseError

        assert BloomDatabaseError is not None

    def test_import_bloom_validation_error(self):
        """Test BloomValidationError import."""
        from bloom_lims.core.exceptions import BloomValidationError

        assert BloomValidationError is not None


class TestTapdbAdapterImports:
    """Tests for tapdb adapter module."""

    def test_import_tapdb_adapter(self):
        """Test tapdb adapter import."""
        from bloom_lims import tapdb_adapter

        assert tapdb_adapter is not None


class TestSubjectingImports:
    """Tests for subjecting module."""

    def test_import_subjecting(self):
        """Test subjecting module import."""
        from bloom_lims import subjecting

        assert subjecting is not None


class TestDomainObjectSetsImports:
    """Tests for domain object_sets module."""

    def test_import_object_sets(self):
        """Test object_sets module import."""
        from bloom_lims.domain import object_sets

        assert object_sets is not None


class TestBatchOperationsImports:
    """Tests for batch operations imports."""

    def test_import_batch_operations(self):
        """Test batch operations module import."""
        from bloom_lims.core import batch_operations

        assert batch_operations is not None

    def test_import_batch_processor(self):
        """Test BatchProcessor import."""
        from bloom_lims.core.batch_operations import BatchProcessor

        assert BatchProcessor is not None


class TestCacheBackendsImports:
    """Tests for cache backends imports."""

    def test_import_cache_backends(self):
        """Test cache backends module import."""
        from bloom_lims.core import cache_backends

        assert cache_backends is not None


class TestReadReplicasImports:
    """Tests for read replicas imports."""

    def test_import_read_replicas(self):
        """Test read replicas module import."""
        from bloom_lims.core import read_replicas

        assert read_replicas is not None


class TestLineageImports:
    """Tests for lineage imports."""

    def test_import_lineage(self):
        """Test lineage module import."""
        from bloom_lims.core import lineage

        assert lineage is not None


class TestCachedRepositoryImports:
    """Tests for cached repository imports."""

    def test_import_cached_repository(self):
        """Test cached repository module import."""
        from bloom_lims.core import cached_repository

        assert cached_repository is not None


class TestContainersImports:
    """Tests for core containers imports."""

    def test_import_core_containers(self):
        """Test core containers module import."""
        from bloom_lims.core import containers

        assert containers is not None


class TestContentImports:
    """Tests for core content imports."""

    def test_import_core_content(self):
        """Test core content module import."""
        from bloom_lims.core import content

        assert content is not None


class TestCoreWorkflowsImports:
    """Tests for core workflows imports."""

    def test_import_core_workflows(self):
        """Test core workflows module import."""
        from bloom_lims.core import workflows

        assert workflows is not None


class TestBaseObjectsImports:
    """Tests for base objects imports."""

    def test_import_base_objects(self):
        """Test base objects module import."""
        from bloom_lims.core import base_objects

        assert base_objects is not None
