"""Tests for schema checkers (SCHEMA001, SCHEMA002).

Verifies that PydanticNullabilityChecker and PydanticSqlalchemyFieldChecker
have all required methods implemented and don't crash on check_project().
"""

import pytest
from pathlib import Path

from stablestack.checkers.schema.pydantic_nullability import PydanticNullabilityChecker
from stablestack.checkers.schema.pydantic_sqlalchemy_fields import PydanticSqlalchemyFieldChecker


# ── SCHEMA001: PydanticNullabilityChecker ───────────────────────────────


class TestSCHEMA001:
    def setup_method(self):
        self.checker = PydanticNullabilityChecker()

    def test_metadata(self):
        assert self.checker.rule_id == "SCHEMA001"
        assert self.checker.is_project_checker is True

    def test_has_required_methods(self):
        """Regression: these methods were missing, causing AttributeError."""
        assert hasattr(self.checker, "_extract_response_models")
        assert hasattr(self.checker, "_extract_sqlalchemy_models")
        assert hasattr(self.checker, "_check_response_vs_db_nullability")
        assert hasattr(self.checker, "_check_raw_sql_inserts")
        assert hasattr(self.checker, "_check_test_fixtures")

    def test_check_project_no_crash_empty_dir(self, tmp_path):
        """check_project must not crash on a directory with no backend structure."""
        findings = self.checker.check_project(tmp_path)
        assert findings == []

    def test_check_project_no_crash_with_backend(self, tmp_path):
        """check_project must not crash when a models/ dir exists but has no Python."""
        (tmp_path / "models").mkdir()
        findings = self.checker.check_project(tmp_path)
        assert findings == []

    def test_detects_nullable_mismatch(self, tmp_path):
        """Flag when Pydantic requires a field that is nullable in SQLAlchemy."""
        models_dir = tmp_path / "models"
        models_dir.mkdir()

        # SQLAlchemy model with nullable full_name
        (models_dir / "user.py").write_text("""\
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    email = Column(String, nullable=False)
    full_name = Column(String, nullable=True)
""")

        # Pydantic response model requiring full_name
        api_dir = tmp_path / "api"
        api_dir.mkdir()
        (api_dir / "schemas.py").write_text("""\
from pydantic import BaseModel

class UserResponse(BaseModel):
    email: str
    full_name: str
""")

        findings = self.checker.check_project(tmp_path)
        assert len(findings) >= 1
        assert any("full_name" in f.message for f in findings)

    def test_no_finding_when_field_is_optional(self, tmp_path):
        """No finding when Pydantic field is Optional."""
        models_dir = tmp_path / "models"
        models_dir.mkdir()

        (models_dir / "user.py").write_text("""\
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    full_name = Column(String, nullable=True)
""")

        api_dir = tmp_path / "api"
        api_dir.mkdir()
        (api_dir / "schemas.py").write_text("""\
from typing import Optional
from pydantic import BaseModel

class UserResponse(BaseModel):
    full_name: Optional[str] = None
""")

        findings = self.checker.check_project(tmp_path)
        assert len(findings) == 0

    def test_check_file_returns_empty(self, tmp_path):
        """check_file is a no-op for project checkers."""
        p = tmp_path / "dummy.py"
        p.write_text("x = 1")
        assert self.checker.check_file(p, "x = 1") == []


# ── SCHEMA002: PydanticSqlalchemyFieldChecker ───────────────────────────


class TestSCHEMA002:
    def setup_method(self):
        self.checker = PydanticSqlalchemyFieldChecker()

    def test_metadata(self):
        assert self.checker.rule_id == "SCHEMA002"
        assert self.checker.is_project_checker is True

    def test_has_required_methods(self):
        """Regression: these methods were missing, causing AttributeError."""
        assert hasattr(self.checker, "_extract_sqlalchemy_models")
        assert hasattr(self.checker, "_check_pydantic_schemas")

    def test_check_project_no_crash_empty_dir(self, tmp_path):
        """check_project must not crash on a directory with no backend structure."""
        findings = self.checker.check_project(tmp_path)
        assert findings == []

    def test_check_project_no_crash_with_backend(self, tmp_path):
        """check_project must not crash when a models/ dir exists but has no Python."""
        (tmp_path / "models").mkdir()
        findings = self.checker.check_project(tmp_path)
        assert findings == []

    def test_detects_field_mismatch(self, tmp_path):
        """Flag when Pydantic ORM schema references a field that doesn't exist in SQLAlchemy."""
        models_dir = tmp_path / "models"
        models_dir.mkdir()

        (models_dir / "user.py").write_text("""\
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    email = Column(String, nullable=False)
    operation_status = Column(String)
""")

        api_dir = tmp_path / "api"
        api_dir.mkdir()
        (api_dir / "schemas.py").write_text("""\
from pydantic import BaseModel, ConfigDict

class UserSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    email: str
    ad_status: str
""")

        findings = self.checker.check_project(tmp_path)
        assert len(findings) >= 1
        assert any("ad_status" in f.message for f in findings)

    def test_no_finding_when_fields_match(self, tmp_path):
        """No finding when Pydantic fields match SQLAlchemy columns."""
        models_dir = tmp_path / "models"
        models_dir.mkdir()

        (models_dir / "user.py").write_text("""\
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    email = Column(String, nullable=False)
""")

        api_dir = tmp_path / "api"
        api_dir.mkdir()
        (api_dir / "schemas.py").write_text("""\
from pydantic import BaseModel, ConfigDict

class UserSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    email: str
""")

        findings = self.checker.check_project(tmp_path)
        assert len(findings) == 0

    def test_check_file_returns_empty(self, tmp_path):
        """check_file is a no-op for project checkers."""
        p = tmp_path / "dummy.py"
        p.write_text("x = 1")
        assert self.checker.check_file(p, "x = 1") == []
