import datetime
from typing import Optional, List

from sqlalchemy import and_, exists
from sqlalchemy.sql import false
from sqlalchemy.orm import Session, Query, joinedload

from watcher.db import (
    Staff,
    Shift,
    Problem,
    Schedule,
    Notification,
)
from watcher.enums import (
    ProblemStatus,
    ProblemReason,
    NotificationType,
)


def query_problems_by_schedule(db: Session, schedule_id: int, query: Optional[Query] = None) -> Query:
    if not query:
        query = db.query(Problem)

    return query.join(Shift, Shift.id == Problem.shift_id).filter(Shift.schedule_id == schedule_id)


def query_active_problems(
    db: Session,
    query: Optional[Query] = None,
    reason: Optional[ProblemReason] = None
) -> Query:
    if not query:
        query = db.query(Problem)
    if reason:
        query = query.filter(Problem.reason == reason)

    # TODO: ABC-10881 фильтровать по признаку "не проблема"

    return (
        query.filter(Problem.status != ProblemStatus.resolved)
        .options(joinedload(Problem.staff))
    )


def query_active_problems_without_notifications(
    db: Session,
    notification_type: dict[ProblemReason, NotificationType],
    current_now: datetime.datetime,
) -> Query:
    query = db.query(Problem).filter(false())
    for problem_reason, notification_type in notification_type.items():
        query = (
            query.union(
                query_active_problems(db=db, reason=problem_reason)
                .join(Shift, Shift.id == Problem.shift_id)
                .join(Schedule, Schedule.id == Shift.schedule_id)
                .filter(
                    Schedule.days_for_notify_of_problems.isnot(None),
                    Shift.start - current_now <= Schedule.days_for_notify_of_problems,
                    ~exists().where(
                        and_(
                            Notification.shift_id == Problem.shift_id,
                            Notification.type == notification_type,
                        )
                    ),
                )
                .options(joinedload(Problem.shift).joinedload(Shift.schedule))
            )
        )
    return query


def query_problems_by_shift_ids(
    db: Session,
    shift_ids: List[int],
    query: Optional[Query] = None
) -> Query:
    if not query:
        query = db.query(Problem)

    return query.filter(Problem.shift_id.in_(shift_ids))


def query_active_problems_by_shift_ids(db: Session, shift_ids: List[int]) -> Query:
    #  TODO: ABC-10881 фильтровать по признаку "не проблема"

    query = query_active_problems(db)
    return query_problems_by_shift_ids(db, shift_ids, query)


def query_shifts_without_problems(
    db: Session,
    query: Optional[Query] = None,
) -> Query:
    """ Если у шифта есть хотя-бы одна нерешенная проблема, где дежурные совпадают - он проблемный """
    if not query:
        query = db.query(Shift)

    # TODO: ABC-10881 фильтровать по признаку "не проблема"

    return query.filter(
        ~db.query(Problem).filter(
            Problem.shift_id == Shift.id,
            Problem.staff_id == Staff.id,
            Problem.status != ProblemStatus.resolved,
        ).exists()
    )
