from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import Optional, List
from pydantic import BaseModel
from datetime import datetime, timedelta
import json

from app.database import get_db
from app.models.shift_swap import ShiftSwap
from app.models.assignment import Assignment
from app.models.mass_schedule import MassSchedule
from app.models.mass_template import MassTemplate
from app.models.special_mass import SpecialMass
from app.models.special_event import SpecialEvent
from app.models.asim import Asim
from app.models.position_type import PositionType
from app.models.user import User
from app.core.security import get_current_user, require_pengurus

router = APIRouter(prefix="/api/shift-swaps", tags=["Shift Swaps"])


def _get_asim_id_from_user(current_user: User) -> int:
    """Ambil asim_id dari JWT user — raise 403 kalau tidak terhubung ke ASIM."""
    if not current_user.asim:
        raise HTTPException(status_code=403, detail="Akun tidak terhubung ke data ASIM")
    return current_user.asim.id

class SwapCreate(BaseModel):
    requester_id: int
    offer_schedule_id: int
    offer_position_number: str
    want_schedule_ids: Optional[List[int]] = None
    swap_type: str = 'weekly'
    request_type: str = 'swap'  # 'swap' | 'replace'

class AdminSwapCreate(BaseModel):
    schedule_id: int
    position_number: str

class TakeSwapRequest(BaseModel):
    asim_id: int
    taker_schedule_id: Optional[int] = None
    taker_position_number: Optional[str] = None

class ApprovalRequest(BaseModel):
    approved: bool

def get_week_range(date):
    """Get week range Sabtu-Jumat"""
    weekday = date.weekday()
    days_since_saturday = (weekday - 5) % 7
    week_start = date - timedelta(days=days_since_saturday)
    week_end = week_start + timedelta(days=6)
    return week_start, week_end

def get_schedule_info(schedule_id, position_number, db):
    if not schedule_id:
        return None
    schedule = db.query(MassSchedule).filter(MassSchedule.id == schedule_id).first()
    if not schedule:
        return None
    if schedule.special_mass_id:
        sm = db.query(SpecialMass).filter(SpecialMass.id == schedule.special_mass_id).first()
        event = db.query(SpecialEvent).filter(SpecialEvent.id == sm.event_id).first() if sm else None
        label = f"{event.name} - {sm.name}" if event and sm else "Special"
        time = sm.time if sm else "-"
    else:
        template = db.query(MassTemplate).filter(MassTemplate.id == schedule.template_id).first()
        label = template.name if template else "Misa Mingguan"
        time = template.time if template else "-"
    return {
        "schedule_id": schedule_id,
        "date": str(schedule.date),
        "time": time,
        "label": label,
        "position_number": position_number,
    }

def get_asim_schedules_in_week(asim_id, date, db):
    """Get semua jadwal ASIM dalam week (Sabtu-Jumat)"""
    week_start, week_end = get_week_range(date)
    assignments = db.query(Assignment).join(MassSchedule).filter(
        Assignment.asim_id == asim_id,
        MassSchedule.is_published == True,
        MassSchedule.date >= week_start,
        MassSchedule.date <= week_end,
    ).all()
    return assignments

def _build_week_schedule_entries(assignments: list, exclude_schedule_id: int, db: Session) -> list:
    """Build week schedule info list dengan batch queries (no N+1)."""
    filtered = [a for a in assignments if a.schedule_id != exclude_schedule_id]
    if not filtered:
        return []

    schedule_ids = list({a.schedule_id for a in filtered})
    schedules_map = {
        s.id: s for s in db.query(MassSchedule).filter(MassSchedule.id.in_(schedule_ids)).all()
    }

    template_ids = [s.template_id for s in schedules_map.values() if s.template_id]
    sm_ids = [s.special_mass_id for s in schedules_map.values() if s.special_mass_id]
    templates_map = {
        t.id: t for t in db.query(MassTemplate).filter(MassTemplate.id.in_(template_ids)).all()
    } if template_ids else {}
    sm_map = {
        sm.id: sm for sm in db.query(SpecialMass).filter(SpecialMass.id.in_(sm_ids)).all()
    } if sm_ids else {}

    pt_codes = list({a.position_type_code for a in filtered if a.position_type_code})
    pt_map = {
        pt.code: pt for pt in db.query(PositionType).filter(PositionType.code.in_(pt_codes)).all()
    } if pt_codes else {}

    result = []
    for a in filtered:
        schedule = schedules_map.get(a.schedule_id)
        pt = pt_map.get(a.position_type_code)
        time = "-"
        if schedule:
            if schedule.template_id:
                tmpl = templates_map.get(schedule.template_id)
                time = tmpl.time if tmpl else "-"
            elif schedule.special_mass_id:
                sm = sm_map.get(schedule.special_mass_id)
                time = sm.time if sm else "-"
        result.append({
            "date": str(schedule.date) if schedule else "-",
            "time": time,
            "position_number": a.position_number,
            "position_type_label": pt.label if pt else a.position_type_code,
        })
    return result

def _load_schedule_cache(schedule_ids: list, db: Session) -> tuple:
    """Batch-load MassSchedule, SpecialMass, SpecialEvent, MassTemplate for a list of IDs.
    Returns (schedules_map, sm_map, event_map, tmpl_map)."""
    if not schedule_ids:
        return {}, {}, {}, {}
    schedules_map = {
        s.id: s for s in db.query(MassSchedule).filter(MassSchedule.id.in_(schedule_ids)).all()
    }
    sm_ids = list({s.special_mass_id for s in schedules_map.values() if s.special_mass_id})
    tmpl_ids = list({s.template_id for s in schedules_map.values() if s.template_id})
    sm_map, event_map = {}, {}
    if sm_ids:
        sms = db.query(SpecialMass).filter(SpecialMass.id.in_(sm_ids)).all()
        sm_map = {sm.id: sm for sm in sms}
        ev_ids = list({sm.event_id for sm in sms})
        if ev_ids:
            event_map = {e.id: e for e in db.query(SpecialEvent).filter(SpecialEvent.id.in_(ev_ids)).all()}
    tmpl_map = {
        t.id: t for t in db.query(MassTemplate).filter(MassTemplate.id.in_(tmpl_ids)).all()
    } if tmpl_ids else {}
    return schedules_map, sm_map, event_map, tmpl_map

def _schedule_info_from_cache(schedule_id, position_number, schedules_map, sm_map, event_map, tmpl_map):
    """Build schedule info dict from pre-loaded cache (no DB queries)."""
    if not schedule_id:
        return None
    schedule = schedules_map.get(schedule_id)
    if not schedule:
        return None
    if schedule.special_mass_id:
        sm = sm_map.get(schedule.special_mass_id)
        event = event_map.get(sm.event_id) if sm else None
        label = f"{event.name} - {sm.name}" if event and sm else "Special"
        time = sm.time if sm else "-"
    else:
        template = tmpl_map.get(schedule.template_id)
        label = template.name if template else "Misa Mingguan"
        time = template.time if template else "-"
    return {
        "schedule_id": schedule_id,
        "date": str(schedule.date),
        "time": time,
        "label": label,
        "position_number": position_number,
    }

def _enrich_swaps_batch(swaps: list, db: Session, viewer_asim_id: int = None) -> list:
    """Enrich a list of swaps with batch queries — O(queries) instead of O(N×queries)."""
    if not swaps:
        return []

    # Collect all IDs needed across all swaps
    requester_ids = [s.requester_id for s in swaps if s.requester_id]
    taker_ids = [s.taker_id for s in swaps if s.taker_id]
    all_asim_ids = list(set(requester_ids + taker_ids))
    asim_map = {a.id: a for a in db.query(Asim).filter(Asim.id.in_(all_asim_ids)).all()} if all_asim_ids else {}

    # Collect all schedule IDs
    offer_ids = [s.offer_schedule_id for s in swaps if s.offer_schedule_id]
    taker_ids_sched = [s.taker_schedule_id for s in swaps if s.taker_schedule_id]
    swap_want_map = {}
    want_ids_all = []
    for s in swaps:
        if s.want_schedule_ids:
            ids = json.loads(s.want_schedule_ids)
            swap_want_map[s.id] = ids
            want_ids_all.extend(ids)
    all_sched_ids = list(set(offer_ids + taker_ids_sched + want_ids_all))
    schedules_map, sm_map, event_map, tmpl_map = _load_schedule_cache(all_sched_ids, db)

    # Batch load offer assignments + PositionType for offer_position_type_label
    offer_assign_map = {}
    pt_label_map = {}
    if offer_ids:
        offer_assignments = db.query(Assignment).filter(
            Assignment.schedule_id.in_(offer_ids)
        ).all()
        raw_assign_map = {}
        for a in offer_assignments:
            raw_assign_map[(a.schedule_id, a.position_number)] = a
        pt_codes = list({a.position_type_code for a in offer_assignments if a.position_type_code})
        pt_map = {pt.code: pt for pt in db.query(PositionType).filter(PositionType.code.in_(pt_codes)).all()} if pt_codes else {}
        for (sid, pnum), a in raw_assign_map.items():
            pt = pt_map.get(a.position_type_code)
            pt_label_map[(sid, pnum)] = pt.label if pt else a.position_type_code

    # Viewer-specific: pre-load viewer's assignments once (instead of per-swap)
    viewer_schedule_map = {}  # schedule_id → position_number
    viewer_offer_conflict_ids = set()  # offer schedule IDs where viewer already assigned
    if viewer_asim_id:
        viewer_assignments = db.query(Assignment).join(MassSchedule).filter(
            Assignment.asim_id == viewer_asim_id,
            MassSchedule.is_published == True,
        ).all()
        viewer_schedule_map = {a.schedule.id: a.position_number for a in viewer_assignments}
        viewer_offer_conflict_ids = set(viewer_schedule_map.keys())

    results = []
    for swap in swaps:
        requester = asim_map.get(swap.requester_id) if swap.requester_id else None
        taker = asim_map.get(swap.taker_id) if swap.taker_id else None

        want_schedules = []
        for sid in swap_want_map.get(swap.id, []):
            info = _schedule_info_from_cache(sid, None, schedules_map, sm_map, event_map, tmpl_map)
            if info:
                want_schedules.append(info)

        offer_info = _schedule_info_from_cache(
            swap.offer_schedule_id, swap.offer_position_number, schedules_map, sm_map, event_map, tmpl_map
        )
        taker_info = _schedule_info_from_cache(
            swap.taker_schedule_id, swap.taker_position_number, schedules_map, sm_map, event_map, tmpl_map
        ) if swap.taker_schedule_id else None

        offer_position_type_label = pt_label_map.get(
            (swap.offer_schedule_id, swap.offer_position_number)
        )

        # Viewer conflict + matching — using pre-loaded viewer data (no extra queries)
        conflict_type = None
        matching_schedules = []
        if viewer_asim_id and viewer_asim_id != swap.requester_id and offer_info:
            if swap.offer_schedule_id in viewer_offer_conflict_ids:
                conflict_type = 'conflict'
            elif want_schedules:
                for w in want_schedules:
                    if w['schedule_id'] in viewer_schedule_map:
                        matching_schedules.append({
                            **w,
                            'my_position': viewer_schedule_map[w['schedule_id']]
                        })

        # Week schedule checks — use pre-loaded schedule objects
        taker_week_schedules = []
        requester_week_schedules = []
        if viewer_asim_id and viewer_asim_id != swap.requester_id and offer_info:
            offer_schedule_obj = schedules_map.get(swap.offer_schedule_id)
            if offer_schedule_obj:
                viewer_week = get_asim_schedules_in_week(viewer_asim_id, offer_schedule_obj.date, db)
                taker_week_schedules = _build_week_schedule_entries(viewer_week, swap.offer_schedule_id, db)
        if swap.taker_schedule_id:
            taker_schedule_obj = schedules_map.get(swap.taker_schedule_id)
            if taker_schedule_obj:
                req_week = get_asim_schedules_in_week(swap.requester_id, taker_schedule_obj.date, db)
                requester_week_schedules = _build_week_schedule_entries(req_week, swap.offer_schedule_id, db)

        results.append({
            "id": swap.id,
            "status": swap.status,
            "swap_type": swap.swap_type,
            "request_type": swap.request_type or 'swap',
            "posted_by_admin": swap.posted_by_admin or False,
            "offer_position_type_label": offer_position_type_label,
            "created_at": str(swap.created_at),
            "requester": {
                "id": requester.id,
                "no_asim": requester.no_asim,
                "full_name": requester.full_name,
                "photo": requester.photo,
            } if requester else None,
            "taker": {
                "id": taker.id,
                "no_asim": taker.no_asim,
                "full_name": taker.full_name,
            } if taker else None,
            "offer": offer_info,
            "want_schedules": want_schedules,
            "taker_schedule": taker_info,
            "conflict_type": conflict_type,
            "matching_schedules": matching_schedules,
            "taker_week_schedules": taker_week_schedules,
            "requester_week_schedules": requester_week_schedules,
            "rejection_reason": swap.rejection_reason,
        })
    return results

def enrich_swap(swap: ShiftSwap, db: Session, viewer_asim_id: int = None) -> dict:
    requester = db.query(Asim).filter(Asim.id == swap.requester_id).first() if swap.requester_id else None
    taker = db.query(Asim).filter(Asim.id == swap.taker_id).first() if swap.taker_id else None

    want_schedules = []
    if swap.want_schedule_ids:
        ids = json.loads(swap.want_schedule_ids)
        for sid in ids:
            info = get_schedule_info(sid, None, db)
            if info:
                want_schedules.append(info)

    offer_info = get_schedule_info(swap.offer_schedule_id, swap.offer_position_number, db)
    taker_info = get_schedule_info(swap.taker_schedule_id, swap.taker_position_number, db) if swap.taker_schedule_id else None

    # Ambil label tipe posisi dari offer (untuk tampilan ketika requester = null)
    offer_position_type_label = None
    if swap.offer_schedule_id and swap.offer_position_number:
        offer_assignment = db.query(Assignment).filter(
            Assignment.schedule_id == swap.offer_schedule_id,
            Assignment.position_number == swap.offer_position_number,
        ).first()
        if offer_assignment:
            pt = db.query(PositionType).filter(PositionType.code == offer_assignment.position_type_code).first()
            offer_position_type_label = pt.label if pt else offer_assignment.position_type_code

    # Cek conflict untuk viewer
    conflict_type = None
    matching_schedules = []
    if viewer_asim_id and viewer_asim_id != swap.requester_id and offer_info:
        # Case 1: viewer bertugas di misa yang sama persis
        same_mass = db.query(Assignment).join(MassSchedule).filter(
            Assignment.asim_id == viewer_asim_id,
            MassSchedule.id == swap.offer_schedule_id,
            MassSchedule.is_published == True,
        ).first()
        if same_mass:
            conflict_type = 'conflict'
        else:
            # Cek matching schedules
            if want_schedules:
                viewer_assignments = db.query(Assignment).join(MassSchedule).filter(
                    Assignment.asim_id == viewer_asim_id,
                    MassSchedule.is_published == True,
                ).all()
                viewer_schedule_map = {a.schedule.id: a.position_number for a in viewer_assignments}
                for w in want_schedules:
                    if w['schedule_id'] in viewer_schedule_map:
                        matching_schedules.append({
                            **w,
                            'my_position': viewer_schedule_map[w['schedule_id']]
                        })

    # Cek jadwal taker (viewer) di week yang sama dengan offer schedule — batch queries
    taker_week_schedules = []
    if viewer_asim_id and viewer_asim_id != swap.requester_id and offer_info:
        offer_schedule_obj = db.query(MassSchedule).filter(MassSchedule.id == swap.offer_schedule_id).first()
        if offer_schedule_obj:
            viewer_week = get_asim_schedules_in_week(viewer_asim_id, offer_schedule_obj.date, db)
            taker_week_schedules = _build_week_schedule_entries(viewer_week, swap.offer_schedule_id, db)

    # Cek jadwal requester di week yang sama dengan taker_schedule — batch queries
    requester_week_schedules = []
    if swap.taker_schedule_id:
        taker_schedule = db.query(MassSchedule).filter(MassSchedule.id == swap.taker_schedule_id).first()
        if taker_schedule:
            req_week_assignments = get_asim_schedules_in_week(swap.requester_id, taker_schedule.date, db)
            requester_week_schedules = _build_week_schedule_entries(req_week_assignments, swap.offer_schedule_id, db)

    return {
        "id": swap.id,
        "status": swap.status,
        "swap_type": swap.swap_type,
        "request_type": swap.request_type or 'swap',
        "posted_by_admin": swap.posted_by_admin or False,
        "offer_position_type_label": offer_position_type_label,
        "created_at": str(swap.created_at),
        "requester": {
            "id": requester.id,
            "no_asim": requester.no_asim,
            "full_name": requester.full_name,
            "photo": requester.photo,
        } if requester else None,
        "taker": {
            "id": taker.id,
            "no_asim": taker.no_asim,
            "full_name": taker.full_name,
        } if taker else None,
        "offer": offer_info,
        "want_schedules": want_schedules,
        "taker_schedule": taker_info,
        "conflict_type": conflict_type,
        "matching_schedules": matching_schedules,
        "taker_week_schedules": taker_week_schedules,
        "requester_week_schedules": requester_week_schedules,
        "rejection_reason": swap.rejection_reason,
    }

@router.get("/monitor")
def monitor_swaps(
    status: Optional[str] = None,
    month: Optional[int] = None,
    year: Optional[int] = None,
    db: Session = Depends(get_db)
):
    """Monitoring semua tukar tugas — untuk pengurus/super_admin"""
    from datetime import date
    import calendar as cal

    q = db.query(ShiftSwap)

    if status and status != 'all':
        q = q.filter(ShiftSwap.status == status)

    if month and year:
        first_day = date(year, month, 1)
        last_day = date(year, month, cal.monthrange(year, month)[1])
        q = q.filter(ShiftSwap.created_at >= first_day, ShiftSwap.created_at <= last_day)

    swaps = q.order_by(ShiftSwap.created_at.desc()).all()
    return _enrich_swaps_batch(swaps, db)


@router.get("/")
def get_swaps(
    asim_id: Optional[int] = None,
    date_from: Optional[str] = None,
    date_to: Optional[str] = None,
    db: Session = Depends(get_db),
):
    from datetime import date as date_type
    q = db.query(ShiftSwap).join(MassSchedule, ShiftSwap.offer_schedule_id == MassSchedule.id).filter(
        ShiftSwap.status == 'open'
    )
    if date_from:
        q = q.filter(MassSchedule.date >= date_type.fromisoformat(date_from))
    if date_to:
        q = q.filter(MassSchedule.date <= date_type.fromisoformat(date_to))
    swaps = q.order_by(MassSchedule.date.asc()).all()
    return _enrich_swaps_batch(swaps, db, asim_id)

@router.get("/my-requests")
def get_my_requests(asim_id: int, db: Session = Depends(get_db)):
    swaps = db.query(ShiftSwap).filter(
        ShiftSwap.requester_id == asim_id
    ).order_by(ShiftSwap.created_at.desc()).all()
    return _enrich_swaps_batch(swaps, db, asim_id)

@router.get("/pending-approval")
def get_pending_approval(asim_id: int, db: Session = Depends(get_db)):
    swaps = db.query(ShiftSwap).filter(
        ShiftSwap.requester_id == asim_id,
        ShiftSwap.status == 'pending_approval',
    ).order_by(ShiftSwap.created_at.desc()).all()
    return _enrich_swaps_batch(swaps, db, asim_id)

@router.get("/pending-for-me")
def get_pending_for_me(asim_id: int, db: Session = Depends(get_db)):
    swaps = db.query(ShiftSwap).filter(
        ShiftSwap.taker_id == asim_id,
        ShiftSwap.status.in_(['pending_approval', 'completed', 'rejected', 'cancelled']),
    ).order_by(ShiftSwap.created_at.desc()).all()
    return _enrich_swaps_batch(swaps, db, asim_id)

@router.get("/notification-count")
def get_notification_count(asim_id: int, db: Session = Depends(get_db)):
    from sqlalchemy import func, case
    pending = db.query(ShiftSwap).filter(
        ShiftSwap.requester_id == asim_id,
        ShiftSwap.status == 'pending_approval',
    ).count()
    # Batch: taker-related rejected + cancelled in one query
    taker_rows = db.query(
        ShiftSwap.status,
        func.count(ShiftSwap.id).label('cnt'),
    ).filter(
        ShiftSwap.taker_id == asim_id,
        ShiftSwap.status.in_(['rejected', 'cancelled']),
    ).group_by(ShiftSwap.status).all()
    taker_map = {row.status: row.cnt for row in taker_rows}
    rejected = taker_map.get('rejected', 0)
    cancelled_for_me = taker_map.get('cancelled', 0)
    # Completed dalam 7 hari terakhir — supaya taker tahu swapnya sudah disetujui
    from datetime import date as dt_type
    week_ago = datetime.now() - timedelta(days=7)
    completed_recent = db.query(ShiftSwap).filter(
        ShiftSwap.taker_id == asim_id,
        ShiftSwap.status == 'completed',
        ShiftSwap.completed_at >= week_ago,
    ).count()
    return {
        "count": pending + rejected + cancelled_for_me + completed_recent,
        "pending": pending,
        "rejected": rejected,
        "cancelled_for_me": cancelled_for_me,
        "completed_recent": completed_recent,
    }

@router.post("/")
def create_swap(data: SwapCreate, db: Session = Depends(get_db)):
    offer_schedule = db.query(MassSchedule).filter(MassSchedule.id == data.offer_schedule_id).first()
    if not offer_schedule:
        raise HTTPException(status_code=404, detail="Jadwal tidak ditemukan")

    assignment = db.query(Assignment).filter(
        Assignment.schedule_id == data.offer_schedule_id,
        Assignment.position_number == data.offer_position_number,
        Assignment.asim_id == data.requester_id,
    ).first()
    if not assignment:
        raise HTTPException(status_code=400, detail="Jadwal ini bukan milik Anda!")

    existing = db.query(ShiftSwap).filter(
        ShiftSwap.offer_schedule_id == data.offer_schedule_id,
        ShiftSwap.offer_position_number == data.offer_position_number,
        ShiftSwap.status == 'open',
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Jadwal ini sudah ada di bursa!")

    swap = ShiftSwap(
        requester_id=data.requester_id,
        offer_schedule_id=data.offer_schedule_id,
        offer_position_number=data.offer_position_number,
        want_schedule_ids=json.dumps(data.want_schedule_ids) if data.want_schedule_ids else None,
        swap_type=data.swap_type,
        request_type=data.request_type,
        posted_by_admin=False,
        status='open',
    )
    db.add(swap)
    try:
        db.commit()
        db.refresh(swap)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menyimpan request tukar tugas")
    return enrich_swap(swap, db)

@router.post("/{swap_id}/take")
def take_swap(swap_id: int, data: TakeSwapRequest, db: Session = Depends(get_db)):
    swap = db.query(ShiftSwap).filter(ShiftSwap.id == swap_id).first()
    if not swap or swap.status != 'open':
        raise HTTPException(status_code=404, detail="Request tidak ditemukan atau sudah diambil")
    if swap.requester_id and swap.requester_id == data.asim_id:
        raise HTTPException(status_code=400, detail="Tidak bisa mengambil request sendiri!")

    # Case 1: conflict - taker bertugas di misa yang sama
    same_mass = db.query(Assignment).filter(
        Assignment.schedule_id == swap.offer_schedule_id,
        Assignment.asim_id == data.asim_id,
    ).first()
    if same_mass:
        raise HTTPException(status_code=400, detail="Anda sudah bertugas di misa ini!")

    # Case 2: cek apakah requester bertugas di week yang sama dengan taker_schedule
    # Untuk replace type: tidak perlu taker_schedule, langsung selesai tanpa approval
    needs_approval = False
    if swap.request_type != 'replace' and data.taker_schedule_id:
        taker_schedule = db.query(MassSchedule).filter(MassSchedule.id == data.taker_schedule_id).first()
        if taker_schedule:
            req_week_assignments = get_asim_schedules_in_week(swap.requester_id, taker_schedule.date, db)
            req_week_assignments = [a for a in req_week_assignments if a.schedule_id != swap.offer_schedule_id]
            if req_week_assignments:
                needs_approval = True

    swap.taker_id = data.asim_id
    swap.taker_schedule_id = data.taker_schedule_id
    swap.taker_position_number = data.taker_position_number

    if needs_approval:
        swap.status = 'pending_approval'
        try:
            db.commit()
        except Exception:
            db.rollback()
            raise HTTPException(status_code=500, detail="Gagal menyimpan proposal tukar tugas")
        return {"message": "Request dikirim! Menunggu persetujuan dari requester.", "needs_approval": True}
    else:
        _do_swap(swap, data.asim_id, db)
        return {"message": "Tukar tugas berhasil!", "needs_approval": False}

def _do_swap(swap: ShiftSwap, taker_asim_id: int, db: Session):
    offer_assignment = db.query(Assignment).filter(
        Assignment.schedule_id == swap.offer_schedule_id,
        Assignment.position_number == swap.offer_position_number,
    ).first()
    if offer_assignment:
        offer_assignment.asim_id = taker_asim_id
        offer_assignment.is_override = True

    if swap.taker_schedule_id and swap.taker_position_number:
        taker_assignment = db.query(Assignment).filter(
            Assignment.schedule_id == swap.taker_schedule_id,
            Assignment.position_number == swap.taker_position_number,
        ).first()
        if taker_assignment:
            taker_assignment.asim_id = swap.requester_id
            taker_assignment.is_override = True

        # Opsi B: auto-cancel swap lain yang menawarkan jadwal yang sama
        stale_swaps = db.query(ShiftSwap).filter(
            ShiftSwap.offer_schedule_id == swap.taker_schedule_id,
            ShiftSwap.offer_position_number == swap.taker_position_number,
            ShiftSwap.status.in_(['open', 'pending_approval']),
            ShiftSwap.id != swap.id,
        ).all()
        for s in stale_swaps:
            s.status = 'cancelled'

    swap.status = 'completed'
    swap.completed_at = datetime.now()
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal menyelesaikan tukar tugas")

@router.post("/{swap_id}/approve")
def approve_swap(
    swap_id: int,
    data: ApprovalRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    swap = db.query(ShiftSwap).filter(ShiftSwap.id == swap_id).first()
    if not swap or swap.status != 'pending_approval':
        raise HTTPException(status_code=404, detail="Request tidak ditemukan")

    # Hanya requester (pemilik jadwal) yang boleh approve/reject
    if current_user.role == "asim":
        my_asim_id = _get_asim_id_from_user(current_user)
        if swap.requester_id != my_asim_id:
            raise HTTPException(status_code=403, detail="Bukan request milikmu")

    if data.approved:
        _do_swap(swap, swap.taker_id, db)
        return {"message": "Swap disetujui dan berhasil!"}
    else:
        swap.status = 'rejected'
        try:
            db.commit()
        except Exception:
            db.rollback()
            raise HTTPException(status_code=500, detail="Gagal menolak swap")
        return {"message": "Swap ditolak."}

@router.get("/admin/schedules")
def admin_get_schedules(date: str = Query(...), db: Session = Depends(get_db), _=Depends(require_pengurus)):
    """Ambil semua jadwal published pada tanggal tertentu untuk admin."""
    from datetime import date as dt_module
    d = dt_module.fromisoformat(date)
    schedules = db.query(MassSchedule).filter(
        MassSchedule.date == d,
        MassSchedule.is_published == True,
    ).all()
    result = []
    for s in schedules:
        info = get_schedule_info(s.id, None, db)
        if info:
            result.append({"id": s.id, "date": info["date"], "time": info["time"], "label": info["label"]})
    return sorted(result, key=lambda x: x["time"])


@router.get("/admin/schedules/{schedule_id}/assignments")
def admin_get_assignments(schedule_id: int, db: Session = Depends(get_db), _=Depends(require_pengurus)):
    """Ambil semua posisi dalam sebuah jadwal beserta siapa yang bertugas — batch queries."""
    assignments = db.query(Assignment).filter(Assignment.schedule_id == schedule_id).all()
    if not assignments:
        return []

    # Batch: pre-fetch all Asim and PositionType in one query each
    asim_ids = [a.asim_id for a in assignments if a.asim_id]
    pt_codes = list({a.position_type_code for a in assignments if a.position_type_code})

    asim_map = {a.id: a for a in db.query(Asim).filter(Asim.id.in_(asim_ids)).all()} if asim_ids else {}
    pt_map = {pt.code: pt for pt in db.query(PositionType).filter(PositionType.code.in_(pt_codes)).all()} if pt_codes else {}

    result = []
    for a in assignments:
        asim = asim_map.get(a.asim_id) if a.asim_id else None
        pt = pt_map.get(a.position_type_code)
        result.append({
            "position_number": a.position_number,
            "position_type_code": a.position_type_code,
            "position_type_label": pt.label if pt else a.position_type_code,
            "asim_id": a.asim_id,
            "asim_name": asim.full_name if asim else None,
            "no_asim": asim.no_asim if asim else None,
        })
    result.sort(key=lambda x: int(x["position_number"]) if str(x["position_number"]).isdigit() else 0)
    return result


@router.post("/admin-post")
def admin_post_swap(data: AdminSwapCreate, db: Session = Depends(get_db), _=Depends(require_pengurus)):
    """Admin post ke bursa atas nama ASIM/Suster yang bertugas di posisi tersebut.
    Jika posisi tidak memiliki ASIM (mis. SR/Suster), requester_id = null."""
    assignment = db.query(Assignment).filter(
        Assignment.schedule_id == data.schedule_id,
        Assignment.position_number == data.position_number,
    ).first()
    if not assignment:
        raise HTTPException(status_code=400, detail="Posisi tidak ditemukan di jadwal ini!")

    existing = db.query(ShiftSwap).filter(
        ShiftSwap.offer_schedule_id == data.schedule_id,
        ShiftSwap.offer_position_number == data.position_number,
        ShiftSwap.status == 'open',
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Posisi ini sudah ada di bursa!")

    schedule = db.query(MassSchedule).filter(MassSchedule.id == data.schedule_id).first()
    swap_type = 'special' if schedule and schedule.special_mass_id else 'weekly'

    swap = ShiftSwap(
        requester_id=assignment.asim_id,  # bisa None untuk posisi SR/non-ASIM
        offer_schedule_id=data.schedule_id,
        offer_position_number=data.position_number,
        swap_type=swap_type,
        request_type='replace',
        posted_by_admin=True,
        status='open',
    )
    db.add(swap)
    try:
        db.commit()
        db.refresh(swap)
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal post ke bursa")
    return enrich_swap(swap, db)


@router.delete("/{swap_id}")
def cancel_swap(
    swap_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    swap = db.query(ShiftSwap).filter(
        ShiftSwap.id == swap_id,
        ShiftSwap.status.in_(['open', 'pending_approval']),
    ).first()
    if not swap:
        raise HTTPException(status_code=404, detail="Request tidak ditemukan")

    # Admin-posted swap dengan requester_id null: hanya pengurus/super_admin yang bisa cancel
    if swap.requester_id is None:
        if current_user.role not in ('pengurus', 'super_admin'):
            raise HTTPException(status_code=403, detail="Hanya admin yang bisa membatalkan request ini")
    else:
        # ASIM biasa hanya bisa cancel miliknya sendiri
        if current_user.role == 'asim':
            my_asim_id = _get_asim_id_from_user(current_user)
            if swap.requester_id != my_asim_id:
                raise HTTPException(status_code=403, detail="Bukan request milikmu")

    swap.status = 'cancelled'
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal membatalkan request")
    return {"message": "Request dibatalkan"}


class AdminManualSwapCreate(BaseModel):
    schedule_a_id: int
    position_a: str
    schedule_b_id: int
    position_b: str

@router.post("/admin-manual-swap")
def admin_manual_swap(data: AdminManualSwapCreate, db: Session = Depends(get_db), _=Depends(require_pengurus)):
    """Admin langsung tukar dua jadwal tanpa melalui bursa — untuk kasus offline."""
    assignment_a = db.query(Assignment).filter(
        Assignment.schedule_id == data.schedule_a_id,
        Assignment.position_number == data.position_a,
    ).first()
    assignment_b = db.query(Assignment).filter(
        Assignment.schedule_id == data.schedule_b_id,
        Assignment.position_number == data.position_b,
    ).first()

    if not assignment_a or not assignment_b:
        raise HTTPException(status_code=404, detail="Posisi tidak ditemukan")
    if assignment_a.asim_id == assignment_b.asim_id:
        raise HTTPException(status_code=400, detail="Kedua posisi memiliki ASIM yang sama")

    schedule_a = db.query(MassSchedule).filter(MassSchedule.id == data.schedule_a_id).first()
    swap_type = 'special' if (schedule_a and schedule_a.special_mass_id) else 'weekly'

    asim_a = assignment_a.asim_id
    asim_b = assignment_b.asim_id

    # Tukar asim_id kedua assignment
    assignment_a.asim_id = asim_b
    assignment_a.is_override = True
    assignment_b.asim_id = asim_a
    assignment_b.is_override = True

    # Buat audit record sebagai completed swap
    swap = ShiftSwap(
        requester_id=asim_a,
        offer_schedule_id=data.schedule_a_id,
        offer_position_number=data.position_a,
        taker_id=asim_b,
        taker_schedule_id=data.schedule_b_id,
        taker_position_number=data.position_b,
        swap_type=swap_type,
        request_type='swap',
        posted_by_admin=True,
        status='completed',
        completed_at=datetime.now(),
    )
    db.add(swap)
    try:
        db.commit()
    except Exception:
        db.rollback()
        raise HTTPException(status_code=500, detail="Gagal melakukan pertukaran")
    return {"message": "Pertukaran berhasil dilakukan"}
