from __future__ import annotations

from datetime import date, datetime, time, timedelta
from zoneinfo import ZoneInfo

from sqlalchemy.orm import Session

from ..config import settings
from .academic import get_active_school_year
from ..models import (
    AcademicPeriodWindow,
    CoordinationRequest,
    Evaluation,
    EvaluationWorkflow,
    PeriodPlanning,
    TeacherAssignment,
)


PERIOD_NAMES = ("I Momento", "II Momento", "III Momento", "Revisión")

REQUEST_LABELS = {
    "planning_late": "Habilitación extemporánea de planificación",
    "planning_modification": "Modificación de planificación mediante acta",
    "evaluation_config_late": "Habilitación extemporánea de configuración",
    "config_modification": "Modificación de evaluación mediante acta",
    "grade_extension": "Prórroga escrita para carga de notas",
}


def institution_now() -> datetime:
    """Naive institutional local time, suitable for existing DateTime columns."""
    try:
        return datetime.now(ZoneInfo(settings.institution_timezone)).replace(tzinfo=None)
    except Exception:
        return datetime.utcnow()


def get_or_create_workflow(evaluation: Evaluation, db: Session) -> EvaluationWorkflow:
    workflow = (
        db.query(EvaluationWorkflow)
        .filter(EvaluationWorkflow.evaluation_id == evaluation.id)
        .first()
    )
    if workflow:
        return workflow
    workflow = EvaluationWorkflow(
        evaluation_id=evaluation.id,
        configured_at=institution_now(),
        config_locked_at=institution_now(),
        completed_at=evaluation.published_at if evaluation.status == "published" else None,
    )
    db.add(workflow)
    db.flush()
    return workflow


def period_window(db: Session, period: str) -> AcademicPeriodWindow | None:
    active_year = get_active_school_year(db)
    return (
        db.query(AcademicPeriodWindow)
        .filter(
            AcademicPeriodWindow.school_year == active_year.name,
            AcademicPeriodWindow.name == period,
            AcademicPeriodWindow.is_active.is_(True),
        )
        .first()
    )


def planning_window_open(window: AcademicPeriodWindow | None, on_date: date | None = None) -> bool:
    if not window:
        return False
    current = on_date or institution_now().date()
    return window.planning_start <= current <= window.planning_end


def evaluation_config_window_open(
    window: AcademicPeriodWindow | None, on_date: date | None = None
) -> bool:
    if not window:
        return False
    current = on_date or institution_now().date()
    return window.evaluation_config_start <= current <= window.evaluation_config_end


def find_assignment(
    db: Session, teacher_id: int, section_id: int, subject_id: int
) -> TeacherAssignment | None:
    return (
        db.query(TeacherAssignment)
        .filter_by(teacher_id=teacher_id, section_id=section_id, subject_id=subject_id)
        .first()
    )


def pending_article112(
    db: Session,
    section_id: int,
    subject_id: int,
    *,
    exclude_evaluation_id: int | None = None,
) -> Evaluation | None:
    query = db.query(Evaluation).filter(
        Evaluation.section_id == section_id,
        Evaluation.subject_id == subject_id,
        Evaluation.is_article112.is_(True),
        Evaluation.status != "published",
    )
    if exclude_evaluation_id:
        query = query.filter(Evaluation.id != exclude_evaluation_id)
    return query.order_by(Evaluation.id).first()


def planning_for_assignment(
    db: Session, teacher_id: int, section_id: int, subject_id: int, period: str
) -> PeriodPlanning | None:
    active_year = get_active_school_year(db)
    return (
        db.query(PeriodPlanning)
        .filter_by(
            teacher_id=teacher_id,
            section_id=section_id,
            subject_id=subject_id,
            period=period,
            school_year_id=active_year.id,
        )
        .first()
    )


def approved_request(
    db: Session,
    *,
    requester_id: int,
    request_type: str,
    target_type: str,
    target_id: int | None = None,
    period: str | None = None,
    require_current_date: bool = True,
) -> CoordinationRequest | None:
    query = db.query(CoordinationRequest).filter(
        CoordinationRequest.requester_id == requester_id,
        CoordinationRequest.request_type == request_type,
        CoordinationRequest.target_type == target_type,
        CoordinationRequest.status == "approved",
        CoordinationRequest.consumed_at.is_(None),
    )
    if target_id is not None:
        query = query.filter(CoordinationRequest.target_id == target_id)
    if period is not None:
        query = query.filter(CoordinationRequest.period == period)
    request = query.order_by(CoordinationRequest.id.desc()).first()
    if not request:
        return None
    if require_current_date and request.requested_new_date:
        if request.requested_new_date < institution_now():
            return None
    return request


def effective_grade_deadline(evaluation: Evaluation, workflow: EvaluationWorkflow) -> datetime | None:
    if workflow.extended_due_at:
        return workflow.extended_due_at
    if not workflow.grades_due_at:
        return None
    if evaluation.is_article112 and evaluation.article112_deadline:
        legal_deadline = datetime.combine(evaluation.article112_deadline, time(23, 59, 59))
        return min(workflow.grades_due_at, legal_deadline)
    return workflow.grades_due_at


def grade_entry_state(evaluation: Evaluation, workflow: EvaluationWorkflow) -> dict:
    now = institution_now()
    deadline = effective_grade_deadline(evaluation, workflow)
    if evaluation.status == "published":
        return {
            "allowed": False,
            "state": "published",
            "message": "La evaluación ya fue publicada y está cerrada.",
            "deadline": deadline,
            "overdue": False,
        }
    if not workflow.conducted_at:
        return {
            "allowed": False,
            "state": "not_conducted",
            "message": "Debe marcar la evaluación como realizada para iniciar el plazo de 48 horas.",
            "deadline": None,
            "overdue": False,
        }
    if deadline and now > deadline:
        return {
            "allowed": False,
            "state": "overdue",
            "message": "El plazo de carga venció. Se requiere solicitud escrita y nueva fecha aprobada por Coordinación.",
            "deadline": deadline,
            "overdue": True,
        }
    return {
        "allowed": True,
        "state": "open",
        "message": "Carga habilitada dentro del plazo institucional.",
        "deadline": deadline,
        "overdue": False,
    }


def mark_conducted(workflow: EvaluationWorkflow, conducted_at: datetime) -> None:
    workflow.conducted_at = conducted_at
    workflow.grades_due_at = conducted_at + timedelta(hours=settings.grade_upload_hours)
    workflow.extended_due_at = None


def seed_period_windows(db: Session, school_year: str | None = None) -> None:
    """Create editable institutional defaults for an academic year."""
    year_name = school_year or get_active_school_year(db).name
    try:
        start_year, end_year = [int(value) for value in year_name.split("-", 1)]
    except Exception:
        today = institution_now().date()
        start_year, end_year = today.year, today.year + 1

    defaults = {
        "I Momento": (
            date(start_year, 9, 1),
            date(start_year, 10, 15),
            date(start_year, 9, 1),
            date(start_year, 10, 31),
        ),
        "II Momento": (
            date(end_year, 1, 5),
            date(end_year, 2, 15),
            date(end_year, 1, 5),
            date(end_year, 2, 28),
        ),
        "III Momento": (
            date(end_year, 4, 1),
            date(end_year, 5, 15),
            date(end_year, 4, 1),
            date(end_year, 5, 31),
        ),
        "Revisión": (
            date(end_year, 6, 15),
            date(end_year, 7, 31),
            date(end_year, 6, 15),
            date(end_year, 7, 31),
        ),
    }
    for name, values in defaults.items():
        exists = (
            db.query(AcademicPeriodWindow)
            .filter_by(school_year=year_name, name=name)
            .first()
        )
        if not exists:
            db.add(
                AcademicPeriodWindow(
                    school_year=year_name,
                    name=name,
                    planning_start=values[0],
                    planning_end=values[1],
                    evaluation_config_start=values[2],
                    evaluation_config_end=values[3],
                )
            )
    db.commit()
