"""
Tests for BLOOM LIMS Pydantic Schemas

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

import pytest
from datetime import datetime, date
from pydantic import ValidationError


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):
        """Test EUID validation function.

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

        cx1 = format_euid("CX", 1)
        cx123 = format_euid("CX", 123)
        wx1000 = format_euid("WX", 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("CX-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("CX", 1, sandbox="X")
        with pytest.raises(ValueError):
            validate_euid(sandbox)


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 bloom_lims.schemas import ContainerCreateSchema
        from daylily_tapdb.euid import format_euid

        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 bloom_lims.schemas import SampleCreateSchema
        from daylily_tapdb.euid import format_euid
        
        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 bloom_lims.schemas import ReagentCreateSchema
        from daylily_tapdb.euid import format_euid
        
        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 bloom_lims.schemas import WorkflowCreateSchema
        from daylily_tapdb.euid import format_euid

        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.workflows import BloomWorkflow
        from bloom_lims.domain.base import BloomObj
        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.files import BloomFile
        from bloom_lims.domain.base import BloomObj
        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 bloom_lims.health import LivenessResponse
        from datetime import datetime

        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
