"""
tests/test_database.py — Database layer tests.

Uses in-memory SQLite. Zero production DB access.
"""
import pytest
import json
from datetime import datetime

from sichgate.database import (
    DatabaseManager, RunRecord, ResultRecord, ScheduleRecord
)
from sichgate.schema import AttackResult, ExecutionTelemetry, now_iso


class TestDatabaseManager:
    """Test SQLite persistence."""

    def test_save_run_creates_record(self, db_manager):
        """Test saving a run to database."""
        summary = {
            "run_id": "test-run-001",
            "model_target": "gpt2",
            "started_at": now_iso(),
            "completed_at": now_iso(),
            "results": [
                {
                    "attack_name": "prompt_injection",
                    "attack_type": "llm",
                    "status": "pass",
                    "severity": "high",
                }
            ]
        }
        
        db_manager.save_run(summary, ["prompt_injection"])
        
        # Verify saved
        with db_manager._SessionFactory() as session:
            run = session.query(RunRecord).filter_by(id="test-run-001").first()
            assert run is not None
            assert run.model_target == "gpt2"

    def test_run_record_fields(self, temp_db):
        """Test RunRecord has all required fields."""
        run = RunRecord(
            id="run-123",
            model_target="vit-base",
            attacks_requested=json.dumps(["simba", "nes"]),
            status="completed",
            summary_json=json.dumps({"results": []})
        )
        
        temp_db.add(run)
        temp_db.commit()
        
        retrieved = temp_db.query(RunRecord).filter_by(id="run-123").first()
        assert retrieved.id == "run-123"
        assert json.loads(retrieved.attacks_requested) == ["simba", "nes"]

    def test_result_record_storage(self, temp_db):
        """Test storing individual attack results."""
        result = ResultRecord(
            id="result-001",
            run_id="run-123",
            attack_name="prompt_injection_direct",
            attack_type="llm",
            status="pass",
            severity="high",
            full_json=json.dumps({
                "input": "test",
                "output": "jailbroken"
            })
        )
        
        temp_db.add(result)
        temp_db.commit()
        
        retrieved = temp_db.query(ResultRecord).filter_by(id="result-001").first()
        assert retrieved.attack_name == "prompt_injection_direct"
        assert json.loads(retrieved.full_json)["status"] == "passed"

    def test_schedule_record_creation(self, temp_db):
        """Test scheduled run configuration storage."""
        schedule = ScheduleRecord(
            id="sched-001",
            model_target="gpt2",
            attacks=json.dumps(["all"]),
            cron_expression="0 2 * * *",  # Daily at 2 AM
            enabled=True,
        )
        
        temp_db.add(schedule)
        temp_db.commit()
        
        retrieved = temp_db.query(ScheduleRecord).filter_by(id="sched-001").first()
        assert retrieved.cron_expression == "0 2 * * *"
        assert retrieved.enabled is True

    def test_multiple_results_per_run(self, db_manager):
        """Test storing multiple attack results for one run."""
        summary = {
            "run_id": "run-multi",
            "model_target": "test_model",
            "started_at": now_iso(),
            "completed_at": now_iso(),
            "results": [
                {
                    "attack_name": "attack_1",
                    "attack_type": "llm",
                    "status": "pass",
                    "severity": "high",
                },
                {
                    "attack_name": "attack_2",
                    "attack_type": "evasion",
                    "status": "fail",
                    "severity": "medium",
                },
                {
                    "attack_name": "attack_3",
                    "attack_type": "extraction",
                    "status": "warning",
                    "severity": "critical",
                },
            ]
        }
        
        db_manager.save_run(summary, ["attack_1", "attack_2", "attack_3"])
        
        with db_manager._SessionFactory() as session:
            results = session.query(ResultRecord).filter_by(run_id="run-multi").all()
            assert len(results) == 3
            assert any(r.attack_name == "attack_1" for r in results)

    def test_query_runs_by_model(self, db_manager):
        """Test filtering runs by target model."""
        summary1 = {
            "run_id": "run-1",
            "model_target": "gpt2",
            "results": []
        }
        summary2 = {
            "run_id": "run-2",
            "model_target": "bert",
            "results": []
        }
        
        db_manager.save_run(summary1, [])
        db_manager.save_run(summary2, [])
        
        with db_manager._SessionFactory() as session:
            gpt2_runs = session.query(RunRecord).filter_by(model_target="gpt2").all()
            assert len(gpt2_runs) == 1
            assert gpt2_runs[0].id == "run-1"

    def test_json_serialization_roundtrip(self, temp_db):
        """Test that JSON data survives save/load."""
        original_data = {
            "attack_type": "llm",
            "severity": "critical",
            "compliance_tags": ["GDPR Art. 32", "HIPAA §164.312"],
            "nested": {"key": "value"}
        }
        
        result = ResultRecord(
            id="json-test",
            run_id="run-json",
            attack_name="test",
            attack_type="llm",
            status="pass",
            severity="critical",
            full_json=json.dumps(original_data)
        )
        
        temp_db.add(result)
        temp_db.commit()
        
        retrieved = temp_db.query(ResultRecord).filter_by(id="json-test").first()
        loaded_data = json.loads(retrieved.full_json)
        
        assert loaded_data == original_data


class TestDatabaseConstraints:
    """Test database integrity."""

    def test_run_id_is_unique(self, temp_db):
        """Run IDs must be unique."""
        run1 = RunRecord(
            id="dup-id",
            model_target="model1",
            attacks_requested="[]",
            status="completed",
            summary_json="{}"
        )
        run2 = RunRecord(
            id="dup-id",  # Duplicate
            model_target="model2",
            attacks_requested="[]",
            status="completed",
            summary_json="{}"
        )
        
        temp_db.add(run1)
        temp_db.commit()
        
        temp_db.add(run2)
        with pytest.raises(Exception):  # Should raise integrity error
            temp_db.commit()

    def test_timestamp_auto_populated(self, temp_db):
        """created_at should auto-populate."""
        run = RunRecord(
            id="ts-test",
            model_target="model",
            attacks_requested="[]",
            status="completed",
            summary_json="{}"
        )
        
        temp_db.add(run)
        temp_db.commit()
        
        retrieved = temp_db.query(RunRecord).filter_by(id="ts-test").first()
        assert retrieved.created_at is not None
        assert isinstance(retrieved.created_at, datetime)


class TestDatabaseMigrations:
    """Test adding new tables/fields."""

    def test_new_schedule_table_exists(self, temp_db):
        """Verify ScheduleRecord table was created."""
        schedule = ScheduleRecord(
            id="migrate-test",
            model_target="test",
            attacks="[]",
            cron_expression="* * * * *",
        )
        
        temp_db.add(schedule)
        temp_db.commit()
        
        # If this passes, table exists
        retrieved = temp_db.query(ScheduleRecord).filter_by(id="migrate-test").first()
        assert retrieved is not None
