"""Tests for spec validators."""

from __future__ import annotations

from prisme.spec.auth import AuthConfig, Role
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec, RelationshipSpec
from prisme.spec.project import ProjectSpec
from prisme.spec.stack import StackSpec
from prisme.spec.validators import (
    validate_auth_config,
    validate_model_relationships,
    validate_stack,
)


def _make_user_model(
    fields: list[FieldSpec] | None = None,
    name: str = "User",
) -> ModelSpec:
    """Helper to create a user model for auth testing."""
    if fields is None:
        fields = [
            FieldSpec(name="email", type=FieldType.STRING, required=True),
            FieldSpec(name="password_hash", type=FieldType.STRING, hidden=True),
            FieldSpec(name="is_active", type=FieldType.BOOLEAN, default=True),
            FieldSpec(name="roles", type=FieldType.JSON, default=[]),
        ]
    return ModelSpec(name=name, fields=fields)


class TestValidateAuthConfig:
    """Tests for validate_auth_config."""

    def test_auth_disabled_no_errors(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[],
        )
        project = ProjectSpec(name="test", auth=AuthConfig(enabled=False))
        errors = validate_auth_config(stack, project)
        assert errors == []

    def test_api_key_preset_no_user_required(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[],
        )
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(enabled=True, preset="api_key"),
        )
        errors = validate_auth_config(stack, project)
        assert errors == []

    def test_jwt_preset_missing_user_model(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Item",
                    fields=[FieldSpec(name="name", type=FieldType.STRING)],
                )
            ],
        )
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(enabled=True, user_model="User"),
        )
        errors = validate_auth_config(stack, project)
        assert any("not found" in e for e in errors)

    def test_jwt_preset_missing_required_fields(self) -> None:
        user = ModelSpec(
            name="User",
            fields=[
                FieldSpec(name="email", type=FieldType.STRING),
                # Missing: password_hash, is_active, roles
            ],
        )
        stack = StackSpec(name="test", version="1.0.0", models=[user])
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(enabled=True, user_model="User"),
        )
        errors = validate_auth_config(stack, project)
        assert any("missing required fields" in e for e in errors)

    def test_password_hash_not_hidden(self) -> None:
        user = ModelSpec(
            name="User",
            fields=[
                FieldSpec(name="email", type=FieldType.STRING),
                FieldSpec(name="password_hash", type=FieldType.STRING, hidden=False),
                FieldSpec(name="is_active", type=FieldType.BOOLEAN),
                FieldSpec(name="roles", type=FieldType.JSON),
            ],
        )
        stack = StackSpec(name="test", version="1.0.0", models=[user])
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(enabled=True, user_model="User"),
        )
        errors = validate_auth_config(stack, project)
        assert any("hidden=True" in e for e in errors)

    def test_valid_user_model_no_errors(self) -> None:
        user = _make_user_model()
        stack = StackSpec(name="test", version="1.0.0", models=[user])
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(enabled=True, user_model="User"),
        )
        errors = validate_auth_config(stack, project)
        assert errors == []

    def test_default_role_not_in_roles(self) -> None:
        user = _make_user_model()
        stack = StackSpec(name="test", version="1.0.0", models=[user])
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(
                enabled=True,
                user_model="User",
                roles=[Role(name="admin")],
                default_role="nonexistent",
            ),
        )
        errors = validate_auth_config(stack, project)
        assert any("not defined" in e for e in errors)

    def test_no_project_spec_creates_default_auth(self) -> None:
        stack = StackSpec(name="test", version="1.0.0", models=[])
        errors = validate_auth_config(stack, project=None)
        # Default auth is disabled, so no errors
        assert errors == []


class TestValidateModelRelationships:
    """Tests for validate_model_relationships."""

    def test_valid_relationships(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Customer",
                    fields=[FieldSpec(name="name", type=FieldType.STRING)],
                    relationships=[
                        RelationshipSpec(
                            name="orders",
                            target_model="Order",
                            type="one_to_many",
                        )
                    ],
                ),
                ModelSpec(
                    name="Order",
                    fields=[FieldSpec(name="total", type=FieldType.FLOAT)],
                ),
            ],
        )
        errors = validate_model_relationships(stack)
        assert errors == []

    def test_invalid_relationship_target(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Customer",
                    fields=[FieldSpec(name="name", type=FieldType.STRING)],
                    relationships=[
                        RelationshipSpec(
                            name="orders",
                            target_model="NonExistent",
                            type="one_to_many",
                        )
                    ],
                ),
            ],
        )
        errors = validate_model_relationships(stack)
        assert len(errors) == 1
        assert "non-existent model" in errors[0]

    def test_no_relationships_no_errors(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Item",
                    fields=[FieldSpec(name="name", type=FieldType.STRING)],
                ),
            ],
        )
        errors = validate_model_relationships(stack)
        assert errors == []


class TestValidateStack:
    """Tests for validate_stack."""

    def test_valid_stack(self) -> None:
        user = _make_user_model()
        stack = StackSpec(name="test", version="1.0.0", models=[user])
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(enabled=True, user_model="User"),
        )
        errors = validate_stack(stack, project)
        assert errors == []

    def test_combines_all_validators(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Customer",
                    fields=[FieldSpec(name="name", type=FieldType.STRING)],
                    relationships=[
                        RelationshipSpec(
                            name="orders",
                            target_model="NonExistent",
                            type="one_to_many",
                        )
                    ],
                ),
            ],
        )
        project = ProjectSpec(
            name="test",
            auth=AuthConfig(enabled=True, user_model="User"),
        )
        errors = validate_stack(stack, project)
        # Should have errors from both auth validation and relationship validation
        assert len(errors) >= 2

    def test_validate_stack_without_project(self) -> None:
        stack = StackSpec(
            name="test",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Item",
                    fields=[FieldSpec(name="name", type=FieldType.STRING)],
                ),
            ],
        )
        errors = validate_stack(stack)
        assert errors == []
