import json

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from evalkit.server.db.base import Base
from evalkit.server.db.models import (
    EvalResultModel,
    EvaluationModel,
    EvaluationStatus,
    EvaluationTagModel,
    MetricScoreModel,
    ProjectModel,
)


@pytest.fixture
def db_session():
    engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False})
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()
    yield session
    session.close()
    Base.metadata.drop_all(engine)


def test_create_and_query_project(db_session):
    project = ProjectModel(name="test-project", description="A test project")
    db_session.add(project)
    db_session.commit()

    result = db_session.query(ProjectModel).filter_by(name="test-project").one()
    assert result.name == "test-project"
    assert result.description == "A test project"
    assert result.id is not None
    assert result.created_at is not None


def test_create_evaluation_linked_to_project(db_session):
    project = ProjectModel(name="proj-eval", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(
        project_id=project.id,
        suite_name="my-suite",
        status=EvaluationStatus.pending,
    )
    db_session.add(evaluation)
    db_session.commit()

    result = db_session.query(EvaluationModel).filter_by(project_id=project.id).one()
    assert result.suite_name == "my-suite"
    assert result.status == EvaluationStatus.pending
    assert result.project_id == project.id
    assert result.completed_at is None
    assert result.average_score is None


def test_eval_result_get_test_case_dict(db_session):
    project = ProjectModel(name="proj-result", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(project_id=project.id, suite_name="suite-1")
    db_session.add(evaluation)
    db_session.commit()

    test_case_data = {"input": "What is 2+2?", "expected": "4"}
    eval_result = EvalResultModel(
        evaluation_id=evaluation.id,
        test_case_json=json.dumps(test_case_data),
        overall_score=0.95,
    )
    db_session.add(eval_result)
    db_session.commit()

    result = db_session.query(EvalResultModel).filter_by(evaluation_id=evaluation.id).one()
    assert result.overall_score == 0.95
    retrieved_dict = result.get_test_case_dict()
    assert retrieved_dict == test_case_data
    assert retrieved_dict["input"] == "What is 2+2?"


def test_create_metric_score_linked_to_eval_result(db_session):
    project = ProjectModel(name="proj-metric", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(project_id=project.id, suite_name="suite-metric")
    db_session.add(evaluation)
    db_session.commit()

    eval_result = EvalResultModel(
        evaluation_id=evaluation.id,
        test_case_json=json.dumps({"q": "test"}),
        overall_score=0.8,
    )
    db_session.add(eval_result)
    db_session.commit()

    metric = MetricScoreModel(
        eval_result_id=eval_result.id,
        metric_name="faithfulness",
        score=0.9,
        passed=True,
        reason="Looks faithful",
    )
    db_session.add(metric)
    db_session.commit()

    result = db_session.query(MetricScoreModel).filter_by(eval_result_id=eval_result.id).one()
    assert result.metric_name == "faithfulness"
    assert result.score == 0.9
    assert result.passed  # SQLite stores bools as int (1/0)
    assert result.reason == "Looks faithful"


def test_cascade_delete_project(db_session):
    project = ProjectModel(name="proj-cascade", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(project_id=project.id, suite_name="suite-cascade")
    db_session.add(evaluation)
    db_session.commit()

    eval_result = EvalResultModel(
        evaluation_id=evaluation.id,
        test_case_json=json.dumps({"q": "cascade test"}),
        overall_score=0.7,
    )
    db_session.add(eval_result)
    db_session.commit()

    metric = MetricScoreModel(
        eval_result_id=eval_result.id,
        metric_name="relevance",
        score=0.7,
        passed=False,
        reason="Not relevant",
    )
    db_session.add(metric)
    db_session.commit()

    project_id = project.id
    evaluation_id = evaluation.id
    eval_result_id = eval_result.id

    db_session.delete(project)
    db_session.commit()

    assert db_session.get(ProjectModel, project_id) is None
    assert db_session.get(EvaluationModel, evaluation_id) is None
    assert db_session.get(EvalResultModel, eval_result_id) is None
    assert db_session.query(MetricScoreModel).filter_by(eval_result_id=eval_result_id).count() == 0


def test_evaluation_status_enum_values():
    assert EvaluationStatus.pending == "pending"
    assert EvaluationStatus.running == "running"
    assert EvaluationStatus.complete == "complete"
    assert EvaluationStatus.failed == "failed"


def test_evaluation_status_all_values(db_session):
    project = ProjectModel(name="proj-status", description="")
    db_session.add(project)
    db_session.commit()

    for status in EvaluationStatus:
        eval_obj = EvaluationModel(
            project_id=project.id,
            suite_name=f"suite-{status.value}",
            status=status,
        )
        db_session.add(eval_obj)
    db_session.commit()

    results = db_session.query(EvaluationModel).filter_by(project_id=project.id).all()
    statuses = {r.status for r in results}
    assert EvaluationStatus.pending in statuses
    assert EvaluationStatus.running in statuses
    assert EvaluationStatus.complete in statuses
    assert EvaluationStatus.failed in statuses


def test_create_evaluation_tag(db_session):
    project = ProjectModel(name="tag-proj", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(project_id=project.id, suite_name="rag_qa", status=EvaluationStatus.complete)
    db_session.add(evaluation)
    db_session.commit()

    tag = EvaluationTagModel(evaluation_id=evaluation.id, key="git_commit", value="abc123")
    db_session.add(tag)
    db_session.commit()

    result = db_session.query(EvaluationTagModel).filter_by(evaluation_id=evaluation.id).one()
    assert result.key == "git_commit"
    assert result.value == "abc123"


def test_evaluation_tags_relationship(db_session):
    project = ProjectModel(name="tag-rel-proj", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(project_id=project.id, suite_name="rag_qa", status=EvaluationStatus.complete)
    db_session.add(evaluation)
    db_session.commit()

    db_session.add(EvaluationTagModel(evaluation_id=evaluation.id, key="model", value="gpt-4o"))
    db_session.add(EvaluationTagModel(evaluation_id=evaluation.id, key="branch", value="main"))
    db_session.commit()

    db_session.refresh(evaluation)
    assert len(evaluation.tags) == 2
    tag_dict = {t.key: t.value for t in evaluation.tags}
    assert tag_dict == {"model": "gpt-4o", "branch": "main"}


def test_evaluation_tag_unique_key_per_evaluation(db_session):
    from sqlalchemy.exc import IntegrityError

    project = ProjectModel(name="tag-unique-proj", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(project_id=project.id, suite_name="rag_qa", status=EvaluationStatus.complete)
    db_session.add(evaluation)
    db_session.commit()

    db_session.add(EvaluationTagModel(evaluation_id=evaluation.id, key="model", value="gpt-4o"))
    db_session.commit()

    db_session.add(EvaluationTagModel(evaluation_id=evaluation.id, key="model", value="gpt-4o-mini"))
    with pytest.raises(IntegrityError):
        db_session.commit()
    db_session.rollback()


def test_evaluation_tag_cascade_delete(db_session):
    project = ProjectModel(name="tag-cascade-proj", description="")
    db_session.add(project)
    db_session.commit()

    evaluation = EvaluationModel(project_id=project.id, suite_name="rag_qa", status=EvaluationStatus.complete)
    db_session.add(evaluation)
    db_session.commit()

    db_session.add(EvaluationTagModel(evaluation_id=evaluation.id, key="model", value="gpt-4o"))
    db_session.commit()

    assert db_session.query(EvaluationTagModel).count() == 1
    db_session.delete(evaluation)
    db_session.commit()
    assert db_session.query(EvaluationTagModel).count() == 0
