from datetime import date

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from app.database import Base
from app.models import (
    Evaluation,
    Grade,
    SchoolYear,
    SchoolYearSection,
    Section,
    Student,
    StudentEnrollment,
    Subject,
    User,
)
from app.services.academic import create_school_year, promote_students
from app.services.reports import build_grade_report


def make_db():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    return sessionmaker(bind=engine)()


def seed_academic_structure(db):
    actor = User(username="coord", full_name="Coordinación", role="evaluacion", password_hash="x")
    source_section = Section(name="1ERO A", grade_level="1ERO", letter="A")
    target_section = Section(name="2DO A", grade_level="2DO", letter="A")
    subject = Subject(name="Matemática")
    source_year = SchoolYear(
        name="2026-2027", start_date=date(2026, 9, 1), end_date=date(2027, 7, 31),
        status="active", is_active=True,
    )
    db.add_all([actor, source_section, target_section, subject, source_year])
    db.flush()
    db.add_all([
        SchoolYearSection(school_year_id=source_year.id, section_id=source_section.id, display_name=source_section.name),
        SchoolYearSection(school_year_id=source_year.id, section_id=target_section.id, display_name=target_section.name),
    ])
    student = Student(ci="100", full_name="Estudiante Prueba", section_id=source_section.id)
    db.add(student)
    db.flush()
    enrollment = StudentEnrollment(
        student_id=student.id, school_year_id=source_year.id, section_id=source_section.id, status="enrolled"
    )
    db.add(enrollment)
    db.commit()
    return actor, source_year, source_section, target_section, subject, student


def test_promotion_creates_next_year_enrollment_and_preserves_history():
    db = make_db()
    actor, source_year, source_section, target_section, _, student = seed_academic_structure(db)
    target_year = create_school_year(
        db,
        name="2027-2028",
        start_date=date(2027, 9, 1),
        end_date=date(2028, 7, 31),
        copy_sections_from=source_year,
    )
    db.flush()

    batch = promote_students(
        db,
        actor=actor,
        source_year=source_year,
        target_year=target_year,
        source_section=source_section,
        target_section=target_section,
        student_ids=[student.id],
        result="promoted",
        notes="Promoción ordinaria",
    )
    db.commit()

    source = db.query(StudentEnrollment).filter_by(student_id=student.id, school_year_id=source_year.id).one()
    target = db.query(StudentEnrollment).filter_by(student_id=student.id, school_year_id=target_year.id).one()
    assert source.status == "promoted"
    assert source.section_id == source_section.id
    assert target.status == "enrolled"
    assert target.section_id == target_section.id
    assert batch.records[0].target_enrollment_id == target.id


def test_annual_report_calculates_each_term_and_final_average():
    db = make_db()
    actor, year, section, _, subject, student = seed_academic_structure(db)
    for index, (period, score) in enumerate(
        (("I Momento", 12), ("II Momento", 14), ("III Momento", 16)), start=1
    ):
        evaluation = Evaluation(
            section_id=section.id,
            subject_id=subject.id,
            teacher_id=actor.id,
            school_year_id=year.id,
            period=period,
            evaluation_number=index,
            title=f"Evaluación {index}",
            status="published",
        )
        db.add(evaluation)
        db.flush()
        db.add(Grade(evaluation_id=evaluation.id, student_id=student.id, score=score, updated_by_id=actor.id))
    db.commit()

    report = build_grade_report(
        db, school_year=year, section=section, subject=subject, scope="annual"
    )
    row = report["rows"][0]
    assert row["period_averages"] == {"I Momento": 12.0, "II Momento": 14.0, "III Momento": 16.0}
    assert row["average"] == 14.0
    assert row["status"] == "Aprobado"
