"""
Contract tests for Metadata Governance module.
[AC-IDSMETA-13~22] Verify provider API matches openapi.provider.yaml contract.

Contract Level: L2
Reference: spec/metadata-governance/openapi.provider.yaml
"""

import pytest
from pydantic import ValidationError
from typing import Any


class MetadataSchema:
    """
    [AC-IDSMETA-13] MetadataSchema contract model.
    Matches openapi.provider.yaml MetadataSchema schema.
    """

    def __init__(
        self,
        id: str,
        field_key: str,
        label: str,
        type: str,
        required: bool,
        scope: list[str],
        status: str,
        options: list[str] | None = None,
        default: str | int | float | bool | None = None,
        is_filterable: bool = True,
        is_rank_feature: bool = False,
    ):
        self.id = id
        self.field_key = field_key
        self.label = label
        self.type = type
        self.required = required
        self.scope = scope
        self.status = status
        self.options = options
        self.default = default
        self.is_filterable = is_filterable
        self.is_rank_feature = is_rank_feature

    def validate(self) -> tuple[bool, list[str]]:
        errors = []
        if not self.field_key:
            errors.append("field_key is required")
        if not self.label:
            errors.append("label is required")
        if self.type not in ["string", "number", "boolean", "enum", "array_enum"]:
            errors.append(f"Invalid type: {self.type}")
        if self.status not in ["draft", "active", "deprecated"]:
            errors.append(f"Invalid status: {self.status}")
        if not self.scope:
            errors.append("scope must have at least one item")
        for s in self.scope:
            if s not in ["kb_document", "intent_rule", "script_flow", "prompt_template"]:
                errors.append(f"Invalid scope value: {s}")
        return len(errors) == 0, errors


class MetadataSchemaCreateRequest:
    """
    [AC-IDSMETA-13] Create request contract model.
    """

    VALID_FIELD_KEY_PATTERN = r"^[a-z0-9_]+$"
    VALID_TYPES = ["string", "number", "boolean", "enum", "array_enum"]
    VALID_STATUSES = ["draft", "active", "deprecated"]
    VALID_SCOPES = ["kb_document", "intent_rule", "script_flow", "prompt_template"]

    def __init__(
        self,
        field_key: str,
        label: str,
        type: str,
        required: bool,
        scope: list[str],
        status: str,
        options: list[str] | None = None,
        default: str | int | float | bool | None = None,
        is_filterable: bool = True,
        is_rank_feature: bool = False,
    ):
        self.field_key = field_key
        self.label = label
        self.type = type
        self.required = required
        self.scope = scope
        self.status = status
        self.options = options
        self.default = default
        self.is_filterable = is_filterable
        self.is_rank_feature = is_rank_feature

    def validate(self) -> tuple[bool, list[str]]:
        import re
        errors = []

        if not self.field_key or len(self.field_key) < 1 or len(self.field_key) > 64:
            errors.append("field_key must be 1-64 characters")
        elif not re.match(self.VALID_FIELD_KEY_PATTERN, self.field_key):
            errors.append(f"field_key must match pattern {self.VALID_FIELD_KEY_PATTERN}")

        if not self.label or len(self.label) < 1 or len(self.label) > 64:
            errors.append("label must be 1-64 characters")

        if self.type not in self.VALID_TYPES:
            errors.append(f"type must be one of {self.VALID_TYPES}")

        if self.status not in self.VALID_STATUSES:
            errors.append(f"status must be one of {self.VALID_STATUSES}")

        if not self.scope or len(self.scope) < 1:
            errors.append("scope must have at least one item")
        else:
            for s in self.scope:
                if s not in self.VALID_SCOPES:
                    errors.append(f"Invalid scope value: {s}")

        if self.type in ["enum", "array_enum"] and (not self.options or len(self.options) == 0):
            errors.append(f"type '{self.type}' requires non-empty options")

        if self.options:
            if len(self.options) != len(set(self.options)):
                errors.append("options must have unique values")

        return len(errors) == 0, errors


class MetadataSchemaUpdateRequest:
    """
    [AC-IDSMETA-14] Update request contract model.
    """

    VALID_STATUSES = ["draft", "active", "deprecated"]
    VALID_SCOPES = ["kb_document", "intent_rule", "script_flow", "prompt_template"]

    def __init__(
        self,
        label: str | None = None,
        required: bool | None = None,
        options: list[str] | None = None,
        default: str | int | float | bool | None = None,
        scope: list[str] | None = None,
        is_filterable: bool | None = None,
        is_rank_feature: bool | None = None,
        status: str | None = None,
    ):
        self.label = label
        self.required = required
        self.options = options
        self.default = default
        self.scope = scope
        self.is_filterable = is_filterable
        self.is_rank_feature = is_rank_feature
        self.status = status

    def validate(self) -> tuple[bool, list[str]]:
        errors = []

        if self.label is not None and (len(self.label) < 1 or len(self.label) > 64):
            errors.append("label must be 1-64 characters")

        if self.status is not None and self.status not in self.VALID_STATUSES:
            errors.append(f"status must be one of {self.VALID_STATUSES}")

        if self.scope is not None:
            if len(self.scope) < 1:
                errors.append("scope must have at least one item")
            else:
                for s in self.scope:
                    if s not in self.VALID_SCOPES:
                        errors.append(f"Invalid scope value: {s}")

        if self.options is not None:
            if len(self.options) != len(set(self.options)):
                errors.append("options must have unique values")

        return len(errors) == 0, errors


class DecompositionTemplate:
    """
    [AC-IDSMETA-21, AC-IDSMETA-22] DecompositionTemplate contract model.
    """

    VALID_VERSION_PATTERN = r"^v?[0-9]+\.[0-9]+\.[0-9]+$"
    VALID_STATUSES = ["draft", "active", "deprecated"]

    def __init__(
        self,
        id: str,
        name: str,
        template_content: str,
        version: str,
        status: str,
    ):
        self.id = id
        self.name = name
        self.template_content = template_content
        self.version = version
        self.status = status

    def validate(self) -> tuple[bool, list[str]]:
        import re
        errors = []

        if not self.name or len(self.name) < 1 or len(self.name) > 100:
            errors.append("name must be 1-100 characters")

        if not self.template_content or len(self.template_content) < 20:
            errors.append("template_content must be at least 20 characters")

        if not re.match(self.VALID_VERSION_PATTERN, self.version):
            errors.append(f"version must match pattern {self.VALID_VERSION_PATTERN}")

        if self.status not in self.VALID_STATUSES:
            errors.append(f"status must be one of {self.VALID_STATUSES}")

        return len(errors) == 0, errors


class ErrorResponse:
    """
    Error response contract model.
    """

    def __init__(self, code: str, message: str, details: dict[str, Any] | None = None):
        self.code = code
        self.message = message
        self.details = details

    def validate(self) -> tuple[bool, list[str]]:
        errors = []
        if not self.code:
            errors.append("code is required")
        if not self.message:
            errors.append("message is required")
        return len(errors) == 0, errors


class TestMetadataSchemaContract:
    """
    [AC-IDSMETA-13] Test MetadataSchema matches OpenAPI contract.
    """

    def test_required_fields_present(self):
        """MetadataSchema must have all required fields."""
        schema = MetadataSchema(
            id="test-id",
            field_key="grade",
            label="年级",
            type="enum",
            required=True,
            scope=["kb_document"],
            status="active",
        )
        is_valid, errors = schema.validate()
        assert is_valid, f"Validation failed: {errors}"

    def test_field_key_pattern_validation(self):
        """field_key must match ^[a-z0-9_]+$ pattern."""
        valid_keys = ["grade", "subject_name", "type1", "kb_type"]
        for key in valid_keys:
            schema = MetadataSchema(
                id="test-id",
                field_key=key,
                label="Test",
                type="string",
                required=False,
                scope=["kb_document"],
                status="draft",
            )
            is_valid, _ = schema.validate()
            assert is_valid, f"Valid key '{key}' should pass"

    def test_field_key_rejects_invalid(self):
        """field_key must reject invalid patterns."""
        invalid_keys = ["Grade", "subject-name", "test key", "test.key"]
        for key in invalid_keys:
            request = MetadataSchemaCreateRequest(
                field_key=key,
                label="Test",
                type="string",
                required=False,
                scope=["kb_document"],
                status="draft",
            )
            is_valid, _ = request.validate()
            assert not is_valid, f"Invalid key '{key}' should fail"

    def test_type_enum_values(self):
        """type must be one of: string, number, boolean, enum, array_enum."""
        valid_types = ["string", "number", "boolean", "enum", "array_enum"]
        for t in valid_types:
            schema = MetadataSchema(
                id="test-id",
                field_key="test",
                label="Test",
                type=t,
                required=False,
                scope=["kb_document"],
                status="active",
            )
            is_valid, _ = schema.validate()
            assert is_valid, f"Valid type '{t}' should pass"

    def test_type_rejects_invalid(self):
        """type must reject invalid values."""
        schema = MetadataSchema(
            id="test-id",
            field_key="test",
            label="Test",
            type="invalid_type",
            required=False,
            scope=["kb_document"],
            status="active",
        )
        is_valid, _ = schema.validate()
        assert not is_valid

    def test_status_enum_values(self):
        """status must be one of: draft, active, deprecated."""
        valid_statuses = ["draft", "active", "deprecated"]
        for s in valid_statuses:
            schema = MetadataSchema(
                id="test-id",
                field_key="test",
                label="Test",
                type="string",
                required=False,
                scope=["kb_document"],
                status=s,
            )
            is_valid, _ = schema.validate()
            assert is_valid, f"Valid status '{s}' should pass"

    def test_scope_enum_values(self):
        """scope items must be valid."""
        valid_scopes = [
            ["kb_document"],
            ["intent_rule"],
            ["script_flow"],
            ["prompt_template"],
            ["kb_document", "intent_rule"],
        ]
        for scope in valid_scopes:
            schema = MetadataSchema(
                id="test-id",
                field_key="test",
                label="Test",
                type="string",
                required=False,
                scope=scope,
                status="active",
            )
            is_valid, _ = schema.validate()
            assert is_valid, f"Valid scope '{scope}' should pass"

    def test_scope_rejects_invalid(self):
        """scope must reject invalid values."""
        schema = MetadataSchema(
            id="test-id",
            field_key="test",
            label="Test",
            type="string",
            required=False,
            scope=["invalid_scope"],
            status="active",
        )
        is_valid, _ = schema.validate()
        assert not is_valid

    def test_scope_requires_at_least_one(self):
        """scope must have at least one item."""
        schema = MetadataSchema(
            id="test-id",
            field_key="test",
            label="Test",
            type="string",
            required=False,
            scope=[],
            status="active",
        )
        is_valid, _ = schema.validate()
        assert not is_valid


class TestMetadataSchemaCreateRequestContract:
    """
    [AC-IDSMETA-13] Test MetadataSchemaCreateRequest validation.
    """

    def test_valid_create_request(self):
        """Valid create request should pass."""
        request = MetadataSchemaCreateRequest(
            field_key="grade",
            label="年级",
            type="enum",
            required=True,
            scope=["kb_document"],
            status="draft",
            options=["初一", "初二", "初三"],
        )
        is_valid, errors = request.validate()
        assert is_valid, f"Validation failed: {errors}"

    def test_enum_type_requires_options(self):
        """[AC-IDSMETA-03] enum type requires non-empty options."""
        request = MetadataSchemaCreateRequest(
            field_key="grade",
            label="年级",
            type="enum",
            required=True,
            scope=["kb_document"],
            status="draft",
            options=None,
        )
        is_valid, _ = request.validate()
        assert not is_valid

    def test_array_enum_type_requires_options(self):
        """[AC-IDSMETA-03] array_enum type requires non-empty options."""
        request = MetadataSchemaCreateRequest(
            field_key="subjects",
            label="学科",
            type="array_enum",
            required=False,
            scope=["kb_document"],
            status="draft",
            options=None,
        )
        is_valid, _ = request.validate()
        assert not is_valid

    def test_options_must_be_unique(self):
        """[AC-IDSMETA-03] options must have unique values."""
        request = MetadataSchemaCreateRequest(
            field_key="grade",
            label="年级",
            type="enum",
            required=True,
            scope=["kb_document"],
            status="draft",
            options=["初一", "初一", "初二"],
        )
        is_valid, _ = request.validate()
        assert not is_valid

    def test_field_key_length_constraints(self):
        """field_key must be 1-64 characters."""
        request = MetadataSchemaCreateRequest(
            field_key="",
            label="Test",
            type="string",
            required=False,
            scope=["kb_document"],
            status="draft",
        )
        is_valid, _ = request.validate()
        assert not is_valid

        request = MetadataSchemaCreateRequest(
            field_key="a" * 65,
            label="Test",
            type="string",
            required=False,
            scope=["kb_document"],
            status="draft",
        )
        is_valid, _ = request.validate()
        assert not is_valid

    def test_label_length_constraints(self):
        """label must be 1-64 characters."""
        request = MetadataSchemaCreateRequest(
            field_key="test",
            label="",
            type="string",
            required=False,
            scope=["kb_document"],
            status="draft",
        )
        is_valid, _ = request.validate()
        assert not is_valid


class TestMetadataSchemaUpdateRequestContract:
    """
    [AC-IDSMETA-14] Test MetadataSchemaUpdateRequest validation.
    """

    def test_valid_update_request(self):
        """Valid update request should pass."""
        request = MetadataSchemaUpdateRequest(
            label="更新后的标签",
            status="deprecated",
        )
        is_valid, errors = request.validate()
        assert is_valid, f"Validation failed: {errors}"

    def test_partial_update(self):
        """Partial update with only some fields should pass."""
        request = MetadataSchemaUpdateRequest(status="active")
        is_valid, _ = request.validate()
        assert is_valid

    def test_empty_update(self):
        """Empty update should pass (all fields optional)."""
        request = MetadataSchemaUpdateRequest()
        is_valid, _ = request.validate()
        assert is_valid

    def test_status_transition_to_deprecated(self):
        """[AC-IDSMETA-14] Status can be updated to deprecated."""
        request = MetadataSchemaUpdateRequest(status="deprecated")
        is_valid, _ = request.validate()
        assert is_valid


class TestDecompositionTemplateContract:
    """
    [AC-IDSMETA-21, AC-IDSMETA-22] Test DecompositionTemplate validation.
    """

    def test_valid_template(self):
        """Valid template should pass."""
        template = DecompositionTemplate(
            id="template-1",
            name="数据拆解模板",
            template_content="这是一个数据拆解模板，用于分析和归类待录入文本...",
            version="1.0.0",
            status="active",
        )
        is_valid, errors = template.validate()
        assert is_valid, f"Validation failed: {errors}"

    def test_version_format_with_v_prefix(self):
        """version can have optional 'v' prefix."""
        template = DecompositionTemplate(
            id="template-1",
            name="Test",
            template_content="a" * 20,
            version="v1.0.0",
            status="active",
        )
        is_valid, _ = template.validate()
        assert is_valid

    def test_version_format_without_v_prefix(self):
        """version can be without 'v' prefix."""
        template = DecompositionTemplate(
            id="template-1",
            name="Test",
            template_content="a" * 20,
            version="2.1.3",
            status="active",
        )
        is_valid, _ = template.validate()
        assert is_valid

    def test_version_rejects_invalid_format(self):
        """version must match semver pattern."""
        invalid_versions = ["1.0", "v1", "1.0.0.0", "latest"]
        for v in invalid_versions:
            template = DecompositionTemplate(
                id="template-1",
                name="Test",
                template_content="a" * 20,
                version=v,
                status="active",
            )
            is_valid, _ = template.validate()
            assert not is_valid, f"Invalid version '{v}' should fail"

    def test_template_content_min_length(self):
        """template_content must be at least 20 characters."""
        template = DecompositionTemplate(
            id="template-1",
            name="Test",
            template_content="short",
            version="1.0.0",
            status="active",
        )
        is_valid, _ = template.validate()
        assert not is_valid

    def test_name_length_constraints(self):
        """name must be 1-100 characters."""
        template = DecompositionTemplate(
            id="template-1",
            name="",
            template_content="a" * 20,
            version="1.0.0",
            status="active",
        )
        is_valid, _ = template.validate()
        assert not is_valid

        template = DecompositionTemplate(
            id="template-1",
            name="a" * 101,
            template_content="a" * 20,
            version="1.0.0",
            status="active",
        )
        is_valid, _ = template.validate()
        assert not is_valid


class TestErrorResponseContract:
    """
    Test ErrorResponse matches OpenAPI contract.
    """

    def test_required_fields(self):
        """ErrorResponse must have code and message."""
        response = ErrorResponse(
            code="VALIDATION_ERROR",
            message="Invalid request",
        )
        is_valid, errors = response.validate()
        assert is_valid, f"Validation failed: {errors}"

    def test_optional_details(self):
        """ErrorResponse can have optional details."""
        response = ErrorResponse(
            code="VALIDATION_ERROR",
            message="Multiple validation errors",
            details={"fields": ["field_key", "label"]},
        )
        is_valid, _ = response.validate()
        assert is_valid


class TestACTraceability:
    """
    [QA-IDSMETA-01] Verify AC traceability in openapi.provider.yaml.
    """

    def test_ac_idsmeta_13_traceability(self):
        """
        [AC-IDSMETA-13] Verify field status management (draft/active/deprecated).
        OpenAPI: listMetadataSchemas, createMetadataSchema
        """
        valid_statuses = ["draft", "active", "deprecated"]
        for status in valid_statuses:
            schema = MetadataSchema(
                id="test-id",
                field_key="test",
                label="Test",
                type="string",
                required=False,
                scope=["kb_document"],
                status=status,
            )
            is_valid, _ = schema.validate()
            assert is_valid

    def test_ac_idsmeta_14_traceability(self):
        """
        [AC-IDSMETA-14] Verify deprecated field handling.
        OpenAPI: updateMetadataSchema with status=deprecated
        """
        request = MetadataSchemaUpdateRequest(status="deprecated")
        is_valid, _ = request.validate()
        assert is_valid

    def test_ac_idsmeta_21_22_traceability(self):
        """
        [AC-IDSMETA-21, AC-IDSMETA-22] Verify decomposition template contract.
        OpenAPI: listDecompositionTemplates, createDecompositionTemplate
        """
        template = DecompositionTemplate(
            id="template-1",
            name="拆解模板",
            template_content="这是一个拆解模板，用于分析待录入文本的归类...",
            version="1.0.0",
            status="active",
        )
        is_valid, _ = template.validate()
        assert is_valid


class TestContractLevelCompliance:
    """
    Verify L2 contract level compliance.
    """

    def test_schema_completeness(self):
        """L2 requires complete schema with required/optional fields clearly defined."""
        schema = MetadataSchema(
            id="test-id",
            field_key="grade",
            label="年级",
            type="enum",
            required=True,
            scope=["kb_document", "intent_rule"],
            status="active",
            options=["初一", "初二", "初三"],
            default="初一",
            is_filterable=True,
            is_rank_feature=False,
        )
        is_valid, _ = schema.validate()
        assert is_valid

    def test_error_response_schema(self):
        """L2 requires defined error response schema."""
        error = ErrorResponse(
            code="SCHEMA_NOT_FOUND",
            message="Metadata schema not found",
            details={"schema_id": "non-existent-id"},
        )
        is_valid, _ = error.validate()
        assert is_valid

    def test_field_validation_rules(self):
        """L2 requires clear field validation rules."""
        request = MetadataSchemaCreateRequest(
            field_key="valid_key_123",
            label="Valid Label",
            type="string",
            required=False,
            scope=["kb_document"],
            status="draft",
        )
        is_valid, errors = request.validate()
        assert is_valid, f"Validation failed: {errors}"
