from app.helpers.crud import get_or_404
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List, Optional
from pydantic import BaseModel
import random

from app.database import get_db
from app.core.security import require_pengurus, get_current_user
from app.models.special_event import SpecialEvent
from app.models.special_mass import SpecialMass
from app.models.special_position import SpecialPosition
from app.models.special_subscription import SpecialSubscription
from app.models.asim import Asim
from app.models.assignment import Assignment
from app.models.mass_schedule import MassSchedule
from app.models.unavailability import Unavailability
from app.models.area_unavailability import AsimAreaUnavailability
from app.schemas.special import (
    SpecialEventCreate, SpecialEventUpdate, SpecialEventResponse,
    SpecialMassCreate, SpecialMassUpdate, SpecialMassResponse,
    SpecialPositionCreate, SpecialPositionResponse, ReorderItem,
    SubscriberResponse,
)

router = APIRouter(prefix="/api/special-events", tags=["Special Events"])


def _enrich_subscribers(mass: SpecialMass) -> List[SubscriberResponse]:
    return [
        SubscriberResponse(
            id=s.id,
            asim_id=s.asim_id,
            asim_name=s.asim.full_name if s.asim else None,
            asim_no=s.asim.no_asim if s.asim else None,
            is_admin_assigned=s.is_admin_assigned,
        )
        for s in mass.subscriptions
    ]


def enrich_positions(positions) -> List[SpecialPositionResponse]:
    return [
        SpecialPositionResponse(
            id=p.id,
            position_number=p.position_number,
            position_type_code=p.position_type_code,
            position_type_label=p.position_type.label if p.position_type else None,
            position_type_color=p.position_type.color if p.position_type else None,
            sort_order=p.sort_order,
            posisi_id=p.posisi_id,
        )
        for p in positions
    ]


def _mass_response(mass: SpecialMass) -> SpecialMassResponse:
    return SpecialMassResponse(
        id=mass.id,
        event_id=mass.event_id,
        name=mass.name,
        date=mass.date,
        time=mass.time,
        mass_type=mass.mass_type,
        max_subscribers=mass.max_subscribers,
        subscriber_count=len(mass.subscriptions),
        is_active=mass.is_active,
        positions=enrich_positions(mass.positions),
        subscribers=_enrich_subscribers(mass),
    )


def enrich_event(event: SpecialEvent) -> SpecialEventResponse:
    return SpecialEventResponse(
        id=event.id,
        name=event.name,
        year=event.year,
        is_active=event.is_active,
        is_published=event.is_published,
        masses=[_mass_response(m) for m in event.masses],
    )

# ── Events ─────────────────────────────────────────────────

@router.get("/", response_model=List[SpecialEventResponse])
def get_all(db: Session = Depends(get_db)):
    events = db.query(SpecialEvent).order_by(SpecialEvent.year.desc(), SpecialEvent.id.desc()).all()
    return [enrich_event(e) for e in events]

@router.get("/{event_id}", response_model=SpecialEventResponse)
def get_one(event_id: int, db: Session = Depends(get_db)):
    event = get_or_404(db, SpecialEvent, event_id, "Event")
    return enrich_event(event)

@router.post("/", response_model=SpecialEventResponse)
def create_event(data: SpecialEventCreate, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    event = SpecialEvent(name=data.name, year=data.year)
    db.add(event)
    try:
        db.commit()
        db.refresh(event)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal membuat event")
    return enrich_event(event)

@router.put("/{event_id}", response_model=SpecialEventResponse)
def update_event(event_id: int, data: SpecialEventUpdate, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    event = get_or_404(db, SpecialEvent, event_id, "Event")

    update_data = data.model_dump(exclude_unset=True)

    if 'is_published' in update_data and update_data['is_published'] == False:
        for mass in event.masses:
            schedule = db.query(MassSchedule).filter(
                MassSchedule.special_mass_id == mass.id
            ).first()
            if schedule:
                schedule.is_published = False

    for key, value in update_data.items():
        setattr(event, key, value)

    try:
        db.commit()
        db.refresh(event)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal mengupdate event")
    return enrich_event(event)

@router.delete("/{event_id}")
def delete_event(event_id: int, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    event = get_or_404(db, SpecialEvent, event_id, "Event")
    for mass in event.masses:
        db.query(SpecialPosition).filter(SpecialPosition.special_mass_id == mass.id).delete()
        db.delete(mass)
    db.delete(event)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menghapus event")
    return {"message": "Event dihapus"}

# ── Masses ─────────────────────────────────────────────────

@router.post("/{event_id}/masses", response_model=SpecialMassResponse)
def add_mass(event_id: int, data: SpecialMassCreate, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    get_or_404(db, SpecialEvent, event_id, "Event")
    mass = SpecialMass(
        event_id=event_id,
        name=data.name,
        date=data.date,
        time=data.time,
        mass_type=data.mass_type,
        max_subscribers=data.max_subscribers if data.mass_type == 'subscribe' else None,
    )
    db.add(mass)
    try:
        db.flush()
        if data.mass_type == 'generate':
            for i in range(1, data.num_positions + 1):
                db.add(SpecialPosition(
                    special_mass_id=mass.id,
                    position_number=str(i),
                    position_type_code='regular',
                    sort_order=i,
                ))
        db.commit()
        db.refresh(mass)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menambahkan acara")
    return _mass_response(mass)

@router.put("/{event_id}/masses/{mass_id}", response_model=SpecialMassResponse)
def update_mass(event_id: int, mass_id: int, data: SpecialMassUpdate, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    mass = db.query(SpecialMass).filter(
        SpecialMass.id == mass_id,
        SpecialMass.event_id == event_id
    ).first()
    if not mass:
        raise HTTPException(status_code=404, detail="Acara tidak ditemukan")
    for key, value in data.model_dump(exclude_unset=True).items():
        setattr(mass, key, value)
    try:
        db.commit()
        db.refresh(mass)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal mengupdate acara")
    return _mass_response(mass)

@router.delete("/{event_id}/masses/{mass_id}")
def delete_mass(event_id: int, mass_id: int, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    mass = db.query(SpecialMass).filter(
        SpecialMass.id == mass_id,
        SpecialMass.event_id == event_id
    ).first()
    if not mass:
        raise HTTPException(status_code=404, detail="Acara tidak ditemukan")
    db.delete(mass)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menghapus acara")
    return {"message": "Acara dihapus"}

# ── Positions ──────────────────────────────────────────────

class PositionSyncItem(BaseModel):
    id: Optional[int] = None
    position_number: str
    position_type_code: str
    sort_order: int
    posisi_id: Optional[int] = None

@router.put("/{event_id}/masses/{mass_id}/positions/sync")
def sync_positions(event_id: int, mass_id: int, data: List[PositionSyncItem], db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    get_or_404(db, SpecialMass, mass_id, "Acara")
    db.query(SpecialPosition).filter(SpecialPosition.special_mass_id == mass_id).delete()
    for item in data:
        db.add(SpecialPosition(
            special_mass_id=mass_id,
            position_number=item.position_number,
            position_type_code=item.position_type_code,
            sort_order=item.sort_order,
            posisi_id=item.posisi_id,
        ))
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menyinkronkan posisi")
    return {"message": "Posisi berhasil disinkronkan"}

@router.put("/{event_id}/masses/{mass_id}/positions/reorder")
def reorder_positions(event_id: int, mass_id: int, data: List[ReorderItem], db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    if not data:
        return {"message": "Reorder berhasil"}
    # Batch: pre-fetch all positions in one query instead of N queries in loop
    ids = [item.id for item in data]
    positions_map = {
        p.id: p for p in db.query(SpecialPosition).filter(
            SpecialPosition.id.in_(ids),
            SpecialPosition.special_mass_id == mass_id,
        ).all()
    }
    for idx, item in enumerate(data):
        p = positions_map.get(item.id)
        if p:
            p.sort_order = idx
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal mereorder posisi")
    return {"message": "Reorder berhasil"}

@router.post("/{event_id}/masses/{mass_id}/positions", response_model=SpecialPositionResponse)
def add_position(event_id: int, mass_id: int, data: SpecialPositionCreate, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    mass = get_or_404(db, SpecialMass, mass_id, "Acara")
    max_sort = len(mass.positions)
    p = SpecialPosition(
        special_mass_id=mass_id,
        position_number=data.position_number,
        position_type_code=data.position_type_code,
        sort_order=max_sort,
    )
    db.add(p)
    try:
        db.commit()
        db.refresh(p)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menambahkan posisi")
    return SpecialPositionResponse(
        id=p.id,
        position_number=p.position_number,
        position_type_code=p.position_type_code,
        position_type_label=p.position_type.label if p.position_type else None,
        position_type_color=p.position_type.color if p.position_type else None,
        sort_order=p.sort_order,
    )

@router.put("/{event_id}/masses/{mass_id}/positions/{position_id}", response_model=SpecialPositionResponse)
def update_position(event_id: int, mass_id: int, position_id: int, data: SpecialPositionCreate, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    p = db.query(SpecialPosition).filter(
        SpecialPosition.id == position_id,
        SpecialPosition.special_mass_id == mass_id
    ).first()
    if not p:
        raise HTTPException(status_code=404, detail="Posisi tidak ditemukan")
    for key, value in data.model_dump().items():
        setattr(p, key, value)
    try:
        db.commit()
        db.refresh(p)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal mengupdate posisi")
    return SpecialPositionResponse(
        id=p.id,
        position_number=p.position_number,
        position_type_code=p.position_type_code,
        position_type_label=p.position_type.label if p.position_type else None,
        position_type_color=p.position_type.color if p.position_type else None,
        sort_order=p.sort_order,
    )

@router.delete("/{event_id}/masses/{mass_id}/positions/{position_id}")
def delete_position(event_id: int, mass_id: int, position_id: int, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    p = db.query(SpecialPosition).filter(
        SpecialPosition.id == position_id,
        SpecialPosition.special_mass_id == mass_id
    ).first()
    if not p:
        raise HTTPException(status_code=404, detail="Posisi tidak ditemukan")
    db.delete(p)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menghapus posisi")
    return {"message": "Posisi dihapus"}

# ── Subscribe endpoints ─────────────────────────────────────

@router.get("/{event_id}/masses/{mass_id}/subscribers")
def get_subscribers(event_id: int, mass_id: int, db: Session = Depends(get_db)):
    mass = db.query(SpecialMass).filter(
        SpecialMass.id == mass_id,
        SpecialMass.event_id == event_id
    ).first()
    if not mass:
        raise HTTPException(status_code=404, detail="Acara tidak ditemukan")
    return _enrich_subscribers(mass)


class SelfSubscribeRequest(BaseModel):
    asim_id: int


@router.post("/{event_id}/masses/{mass_id}/subscribe")
def self_subscribe(event_id: int, mass_id: int, data: SelfSubscribeRequest, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    mass = db.query(SpecialMass).filter(
        SpecialMass.id == mass_id,
        SpecialMass.event_id == event_id,
        SpecialMass.mass_type == 'subscribe',
    ).first()
    if not mass:
        raise HTTPException(status_code=404, detail="Acara tidak ditemukan")
    if mass.max_subscribers and len(mass.subscriptions) >= mass.max_subscribers:
        raise HTTPException(status_code=400, detail="Slot penuh!")
    existing = db.query(SpecialSubscription).filter(
        SpecialSubscription.special_mass_id == mass_id,
        SpecialSubscription.asim_id == data.asim_id,
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Sudah terdaftar!")
    sub = SpecialSubscription(
        special_mass_id=mass_id,
        asim_id=data.asim_id,
        is_admin_assigned=False,
    )
    db.add(sub)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal subscribe")
    return {"message": "Berhasil subscribe!"}


@router.delete("/{event_id}/masses/{mass_id}/subscribe/{asim_id}")
def self_unsubscribe(event_id: int, mass_id: int, asim_id: int, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    sub = db.query(SpecialSubscription).filter(
        SpecialSubscription.special_mass_id == mass_id,
        SpecialSubscription.asim_id == asim_id,
    ).first()
    if not sub:
        raise HTTPException(status_code=404, detail="Tidak ditemukan")
    db.delete(sub)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal unsubscribe")
    return {"message": "Unsubscribe berhasil"}


class AdminAssignRequest(BaseModel):
    asim_id: int


@router.post("/{event_id}/masses/{mass_id}/subscribers", dependencies=[Depends(require_pengurus)])
def admin_assign(event_id: int, mass_id: int, data: AdminAssignRequest, db: Session = Depends(get_db)):
    mass = db.query(SpecialMass).filter(
        SpecialMass.id == mass_id,
        SpecialMass.event_id == event_id,
        SpecialMass.mass_type == 'subscribe',
    ).first()
    if not mass:
        raise HTTPException(status_code=404, detail="Acara tidak ditemukan")
    if mass.max_subscribers and len(mass.subscriptions) >= mass.max_subscribers:
        raise HTTPException(status_code=400, detail="Slot penuh!")
    existing = db.query(SpecialSubscription).filter(
        SpecialSubscription.special_mass_id == mass_id,
        SpecialSubscription.asim_id == data.asim_id,
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="ASIM sudah terdaftar!")
    sub = SpecialSubscription(
        special_mass_id=mass_id,
        asim_id=data.asim_id,
        is_admin_assigned=True,
    )
    db.add(sub)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menambahkan ASIM")
    return {"message": "ASIM berhasil ditambahkan"}


@router.delete("/{event_id}/masses/{mass_id}/subscribers/{asim_id}", dependencies=[Depends(require_pengurus)])
def admin_remove(event_id: int, mass_id: int, asim_id: int, db: Session = Depends(get_db)):
    sub = db.query(SpecialSubscription).filter(
        SpecialSubscription.special_mass_id == mass_id,
        SpecialSubscription.asim_id == asim_id,
    ).first()
    if not sub:
        raise HTTPException(status_code=404, detail="Tidak ditemukan")
    db.delete(sub)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menghapus ASIM dari acara")
    return {"message": "ASIM dihapus dari acara"}

# ── Generate & Publish ─────────────────────────────────────

@router.post("/{event_id}/generate")
def generate_event(event_id: int, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    event = get_or_404(db, SpecialEvent, event_id, "Event")

    all_asim = db.query(Asim).filter(Asim.is_active == True).all()

    eligible_map = {}
    for asim in all_asim:
        if hasattr(asim, 'eligibilities') and isinstance(asim.eligibilities, str):
            eligible_map[asim.id] = [e.strip() for e in asim.eligibilities.split(',') if e.strip()]
        elif hasattr(asim, 'eligibilities') and hasattr(asim.eligibilities, '__iter__'):
            eligible_map[asim.id] = [e.position_type_code for e in asim.eligibilities]
        else:
            eligible_map[asim.id] = []

    unavailabilities = db.query(Unavailability).filter(
        Unavailability.status == 'approved'
    ).all()

    area_unavails = db.query(AsimAreaUnavailability).filter(
        AsimAreaUnavailability.is_active == True,
        AsimAreaUnavailability.status == 'approved',
    ).all()
    area_unavail_map = {}
    for au in area_unavails:
        area_unavail_map.setdefault(au.asim_id, set()).add(au.area_id)

    # Only generate for 'generate' type masses
    generate_masses = [m for m in event.masses if m.mass_type == 'generate']

    try:
        for mass in generate_masses:
            existing_schedule = db.query(MassSchedule).filter(
                MassSchedule.special_mass_id == mass.id,
                MassSchedule.is_published == False,
            ).first()
            if existing_schedule:
                db.query(Assignment).filter(Assignment.schedule_id == existing_schedule.id).delete()
                db.delete(existing_schedule)
        db.flush()

        sorted_masses = sorted(generate_masses, key=lambda m: (m.date, m.time))
        assignment_counts = {}

        for mass in sorted_masses:
            schedule = MassSchedule(
                special_mass_id=mass.id,
                date=mass.date,
                is_published=False,
            )
            db.add(schedule)
            db.flush()

            positions = sorted(mass.positions, key=lambda p: p.sort_order)
            assigned_this_mass = set()

            for pos in positions:
                pt_code = pos.position_type_code
                pos_area_id = pos.posisi.area_id if pos.posisi_id and pos.posisi else None

                if pos.position_type and not pos.position_type.requires_asim:
                    db.add(Assignment(
                        schedule_id=schedule.id,
                        asim_id=None,
                        position_number=pos.position_number,
                        position_type_code=pt_code,
                        is_override=False,
                    ))
                    continue

                def get_candidates():
                    result = []
                    for asim in all_asim:
                        if asim.id in assigned_this_mass:
                            continue
                        from app.routes.schedule import is_unavailable
                        class FakeMassTemplate:
                            time = mass.time
                            day_of_week = mass.date.weekday()
                        if is_unavailable(asim.id, mass.date, FakeMassTemplate(), unavailabilities):
                            continue
                        if pos.position_type and pos.position_type.requires_eligibility:
                            if pt_code not in eligible_map.get(asim.id, []):
                                continue
                        if pos_area_id and pos_area_id in area_unavail_map.get(asim.id, set()):
                            continue
                        result.append(asim)
                    return result

                candidates = get_candidates()

                if not candidates:
                    db.add(Assignment(
                        schedule_id=schedule.id,
                        asim_id=None,
                        position_number=pos.position_number,
                        position_type_code=pt_code,
                        is_override=False,
                    ))
                    continue

                candidates.sort(key=lambda a: assignment_counts.get(a.id, 0))
                min_count = assignment_counts.get(candidates[0].id, 0)
                top_candidates = [a for a in candidates if assignment_counts.get(a.id, 0) == min_count]
                chosen = random.choice(top_candidates)

                db.add(Assignment(
                    schedule_id=schedule.id,
                    asim_id=chosen.id,
                    position_number=pos.position_number,
                    position_type_code=pt_code,
                    is_override=False,
                ))

                assigned_this_mass.add(chosen.id)
                assignment_counts[chosen.id] = assignment_counts.get(chosen.id, 0) + 1

        db.commit()
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal generate jadwal event")

    return {"message": f"Generated {len(sorted_masses)} acara untuk event {event.name}"}


@router.post("/{event_id}/publish")
def publish_event(event_id: int, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    event = get_or_404(db, SpecialEvent, event_id, "Event")

    # Batch: get all schedules for this event's masses in one query
    mass_ids = [m.id for m in event.masses if m.mass_type == 'generate']
    schedules_map = {}
    if mass_ids:
        schedules = db.query(MassSchedule).filter(
            MassSchedule.special_mass_id.in_(mass_ids)
        ).all()
        schedules_map = {s.special_mass_id: s for s in schedules}

    for mass in event.masses:
        if mass.mass_type != 'generate':
            continue
        schedule = schedules_map.get(mass.id)
        if schedule:
            schedule.is_published = True

    event.is_published = True
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal mempublish event")
    return {"message": f"Event {event.name} berhasil dipublish"}


@router.get("/{event_id}/masses/{mass_id}/assignments")
def get_mass_assignments(event_id: int, mass_id: int, db: Session = Depends(get_db)):
    schedule = db.query(MassSchedule).filter(
        MassSchedule.special_mass_id == mass_id,
    ).first()
    if not schedule:
        return []
    assignments = db.query(Assignment).filter(
        Assignment.schedule_id == schedule.id
    ).all()
    return [{"position_number": a.position_number, "asim_id": a.asim_id, "is_override": a.is_override} for a in assignments]


class MassAssignmentUpdate(BaseModel):
    position_number: str
    asim_id: Optional[int] = None


@router.put("/{event_id}/masses/{mass_id}/assignments")
def update_mass_assignment(event_id: int, mass_id: int, data: MassAssignmentUpdate, db: Session = Depends(get_db), _: object = Depends(require_pengurus)):
    schedule = db.query(MassSchedule).filter(
        MassSchedule.special_mass_id == mass_id,
    ).first()
    if not schedule:
        raise HTTPException(status_code=400, detail="Jadwal belum di-generate! Klik Generate dulu.")
    a = db.query(Assignment).filter(
        Assignment.schedule_id == schedule.id,
        Assignment.position_number == data.position_number,
    ).first()
    if not a:
        raise HTTPException(status_code=404, detail="Assignment tidak ditemukan")
    a.asim_id = data.asim_id
    a.is_override = True
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal mengupdate assignment")
    return {"message": "Assignment diupdate"}
