from fastapi import APIRouter, Request, Depends, HTTPException, Query
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session
from typing import List, Optional, Any, Dict
from datetime import datetime
import datetime as dt
import sys
import secrets
import string

from ..database import templates
from ..models.base import get_db
from ..models import reservation_crud as res_crud
from ..models import auth_crud 
from ..auth.deps import current_user, require_docente_or_admin, require_admin
# Models
from .schemas import ReservationIn, ReservationUpdate, ExpenseIn

router = APIRouter()

# --- Html Views ---

@router.get("/reservas", response_class=HTMLResponse)
async def reservas_dashboard(request: Request):
    user = current_user(request)
    if not user:
        return RedirectResponse(url="/login", status_code=303)
    return templates.TemplateResponse("reservas.html", {"request": request, "user": user})


# --- API Endpoints ---

@router.get("/api/reservations/{res_id}")
async def api_reservation_get(request: Request, res_id: int, db: Session = Depends(get_db)):
    require_docente_or_admin(request)
    r = res_crud.get_reservation_by_id(db, res_id)
    if not r:
        raise HTTPException(status_code=404, detail="Reserva no encontrada")
    return res_crud.reservation_to_dict(r)


@router.get("/api/reservations/{res_id}/students")
async def api_reservation_students(request: Request, res_id: int, db: Session = Depends(get_db)):
    require_docente_or_admin(request)
    # This route returns specific student info including payment status within the reservation
    return res_crud.get_reservation_students(db, res_id)


@router.get("/api/reservations", response_model=dict)
async def reservations_paginated(
    request: Request,
    page: int = Query(1, ge=1, description="Página (1-based)"),
    size: int = Query(10, ge=1, le=100, description="Tamaño de página"),
    user_id: Optional[int] = Query(None),
    docente_id: Optional[int] = Query(None),
    status: Optional[str] = Query(None),
    db: Session = Depends(get_db)
):
    require_docente_or_admin(request)
    
    result = res_crud.get_reservations_paginated(
        db, 
        page=page, 
        size=size, 
        user_id=user_id, 
        docente_id=docente_id, 
        status=status
    )
    
    return {
        "items": result["items"],
        "total": result["meta"]["total"],
        "page": result["meta"]["page"],
        "size": result["meta"]["size"],
        "pages": result["meta"]["pages"]
    }


@router.post("/api/reservations", response_model=dict)
async def api_reservations_create(request: Request, payload: ReservationIn, db: Session = Depends(get_db)):
    me = require_docente_or_admin(request)
    try:
        raw = await request.json()
    except Exception:
        raw = {}
        
    print(f"DEBUG: Creating reservation. Payload: {payload.dict()}", file=sys.stderr)

    # Determine date/time
    date_txt = payload.date
    time_txt = payload.time
    
    # Validation
    try:
        dt.date.fromisoformat(date_txt)
    except Exception:
        raise HTTPException(status_code=400, detail="Fecha inválida (usa YYYY-MM-DD).")
    try:
        hh, mm = map(int, time_txt.split(":"))
    except Exception:
        raise HTTPException(status_code=400, detail="Hora inválida (usa HH:MM).")
    if not (0 <= hh < 24 and 0 <= mm < 60):
        raise HTTPException(status_code=400, detail="Hora inválida.")
    if hh == 0 and mm == 0:
        raise HTTPException(status_code=400, detail="No se admiten reservas a medianoche (00:00).")
        
    time_txt = f"{hh:02d}:{mm:02d}"
    dmin = int(payload.duration_minutes)
    if dmin <= 0 or dmin > 24*60:
        raise HTTPException(status_code=400, detail="Duración inválida.")
        
    price_cents = payload.price_cents
    name = payload.name
    email = payload.email
    phone = payload.phone
    status_norm = (payload.status or "confirmada").lower()
    paid_flag = payload.paid
    
    student_ids = payload.user_ids
    if not student_ids and payload.user_id:
        student_ids = [payload.user_id]
        
    # Docente Logic
    docente_id = payload.docente_id
    if docente_id is None:
        docente_id = payload.teacher_id

    if docente_id is None:
        try:
             docente_id = int(raw.get("teacher_id"))
        except Exception:
             pass
             
    if docente_id is not None:
        try:
            docente_id = int(docente_id)
            if docente_id > 0:
                # Import here to avoid circular imports? No, imported fully above as auth_crud
                docente_user = auth_crud.get_user_by_id(db, docente_id)
                if not docente_user or str(docente_user.role).lower() != 'docente':
                     print(f"DEBUG: teacher_id {docente_id} is not a valid docente.", file=sys.stderr)
                     # raise HTTPException(status_code=400, detail="docente_id no corresponde a un docente válido.")
                     # Allow for now or fallback? strict check:
                     raise HTTPException(status_code=400, detail="docente_id no corresponde a un docente válido.")
            else:
                docente_id = None
        except ValueError:
            print(f"DEBUG: teacher_id {docente_id} invalid format.", file=sys.stderr)
            raise HTTPException(status_code=400, detail="docente_id inválido.")
            
    if docente_id is None:
        try:
             # Default to current user who is creating it (likely admin or docente)
             if isinstance(me, dict):
                 docente_id = int(me["id"])
             else:
                 # Handle object or sqlite3.Row
                 docente_id = getattr(me, "id", None)
                 if docente_id is None:
                     # Try dict-like access for Row
                     try:
                        docente_id = int(me["id"])
                     except (KeyError, TypeError, ValueError):
                        docente_id = None
                        
             if docente_id is not None:
                docente_id = int(docente_id)
        except Exception as e:
             print(f"DEBUG: Failed to set default docente_id: {e}", file=sys.stderr)
             pass

    legacy_user_id = student_ids[0] if student_ids and len(student_ids) == 1 else None

    
    # Per student cents logic
    per_student_cents = {}
    try:
        psc = raw.get("per_student_cents") or {}
        if isinstance(psc, dict):
            for k, v in psc.items():
                try:
                    kk = int(k); vv = int(v or 0)
                    per_student_cents[kk] = max(0, vv)
                except: continue
    except: pass
    
    # Student payments logic
    student_payments = payload.student_payments or {}
    if not student_payments:
         try:
             sp = raw.get("student_payments")
             if isinstance(sp, dict):
                 student_payments = {int(k): bool(v) for k, v in sp.items()}
         except: pass

    # Calculate shares
    shares = {}
    if student_ids:
        if per_student_cents:
            for sid in student_ids:
                shares[sid] = max(0, int(per_student_cents.get(sid, price_cents) or 0))
        else:
            for sid in student_ids:
                shares[sid] = max(0, int(price_cents or 0))
        total_price_cents = sum(shares.values())
    else:
         total_price_cents = int(price_cents or 0)

    # 1. Create Reservation
    # Check duplicate?
    dup = res_crud.check_duplicate_reservation(db, date_txt, time_txt, docente_id)
    if dup:
        # Prevent creating multiple reservations for the same teacher at the exact same time
        raise HTTPException(status_code=400, detail="El docente ya tiene una reserva en esa fecha y hora.")

    created_reservation_id = res_crud.create_reservation(
        db,
        date=date_txt,
        time=time_txt,
        duration_minutes=dmin,
        price_cents=total_price_cents,
        name=name,
        email=email,
        phone=phone,
        notes=payload.notes,
        user_id=legacy_user_id,
        status=status_norm,
        docente_id=docente_id or 0,
        paid=1 if paid_flag else 0
    )
    
    # 2. Add students and handle payments
    if student_ids:
        for sid in student_ids:
            is_paid = student_payments.get(sid, paid_flag)
            res_crud.add_student_to_reservation(db, created_reservation_id, sid, paid=1 if is_paid else 0)
            
            # Ensure finance record exists (handled by update_user_finance)
            if is_paid:
                share = shares.get(sid, 0)
                share_eur = round(share / 100.0, 2)
                
                # Get current balance to subtract
                u = auth_crud.get_user_by_id(db, sid)
                if u and u.finance:
                    current_bal = u.finance.matricula_eur
                else:
                    current_bal = 0.0
                    
                new_bal = round(current_bal - share_eur, 2)
                
                auth_crud.update_user_finance(db, sid, matricula_eur=new_bal)
                
                # Create expense record
                if share > 0:
                    concept_base = f"Reserva {date_txt} {time_txt}"
                    res_crud.create_expense(
                        db,
                        date=date_txt,
                        amount_cents=share,
                        category="reserva",
                        concept=concept_base,
                        user_id=sid,
                        reservation_id=created_reservation_id
                    )

    return {"ok": True, "id": created_reservation_id}


@router.patch("/api/reservations/{res_id}", response_model=dict)
async def api_reservations_update(request: Request, res_id: int, payload: ReservationUpdate, db: Session = Depends(get_db)):
    require_docente_or_admin(request)
    
    # Get existing reservation
    reservation = res_crud.get_reservation_by_id(db, res_id)
    if not reservation:
        raise HTTPException(status_code=404, detail="Reserva no encontrada")
        
    try:
        raw = await request.json()
    except Exception:
        raw = {}
        
    updates = payload.dict(exclude_unset=True) if hasattr(payload, "dict") else (payload or {})
    
    # --- Input Normalization & Validation ---
    
    if "date" in updates and updates["date"] is not None:
        try:
            dt.datetime.strptime(updates["date"], "%Y-%m-%d")
        except Exception:
            raise HTTPException(status_code=400, detail="Fecha inválida. Usa formato YYYY-MM-DD.")
            
    if "time" in updates and updates["time"] is not None:
        try:
            t = str(updates["time"]).strip()[:5]
            hh, mm = map(int, t.split(":"))
            updates["time"] = f"{hh:02d}:{mm:02d}"
        except Exception:
            raise HTTPException(status_code=400, detail="Hora inválida. Usa formato HH:MM.")
        if not (0 <= hh < 24 and 0 <= mm < 60):
             raise HTTPException(status_code=400, detail="Hora inválida.")
        if hh == 0 and mm == 0:
             raise HTTPException(status_code=400, detail="No se admiten reservas a medianoche (00:00).")

    if "duration" in raw and "duration_minutes" not in updates:
        try:
             updates["duration_minutes"] = int(raw.get("duration") or 0)
        except: pass
        
    if "duration_minutes" in updates and updates["duration_minutes"] is not None:
        dmin = int(updates["duration_minutes"])
        if dmin <= 0 or dmin > 24*60:
             raise HTTPException(status_code=400, detail="Duración inválida.")

    if "status" in updates and updates["status"] is not None:
        if str(updates["status"]).strip().lower() not in {"pendiente", "confirmada", "cancelada", "completada"}:
             raise HTTPException(status_code=400, detail=f"Estado no permitido")
        updates["status"] = str(updates["status"]).strip().lower()

    # Docente
    if "teacher_id" in raw and "docente_id" not in updates:
        updates["docente_id"] = raw.get("teacher_id")
        
    if "docente_id" in updates and updates["docente_id"] is not None:
        try:
            docente_id = int(updates["docente_id"])
            if docente_id > 0:
                docente = auth_crud.get_user_by_id(db, docente_id)
                if not docente or str(docente.role).lower() != 'docente':
                    raise HTTPException(status_code=400, detail="docente_id no corresponde a un docente válido.")
        except (ValueError, TypeError):
             raise HTTPException(status_code=400, detail="docente_id inválido.")
    # If docente_id is None/empty, don't update it (keep current DB value)
    if "docente_id" in updates and (updates["docente_id"] is None or updates["docente_id"] == ""):
        del updates["docente_id"]

    # Price
    if "price_eur" in raw and "price_cents" not in updates:
        try:
             updates["price_cents"] = int(round(float(str(raw["price_eur"]).replace(",", ".")) * 100))
        except: pass
    elif "price" in raw and "price_cents" not in updates:
        try:
             updates["price_cents"] = int(round(float(str(raw["price"]).replace(",", ".")) * 100))
        except: pass
        
    if "price_cents" in updates:
        try:
             updates["price_cents"] = int(updates["price_cents"] or 0)
        except: updates["price_cents"] = 0
        if updates["price_cents"] < 0:
             raise HTTPException(status_code=400, detail="Precio inválido.")
             
    # Paid
    prev_paid = bool(reservation.paid)
    new_paid = prev_paid
    if "paid" in updates and updates["paid"] is not None:
        new_paid = bool(updates["paid"])
        updates["paid"] = 1 if new_paid else 0
    else:
        val = raw.get("paid", raw.get("has_paid"))
        if val is not None:
            if isinstance(val, bool):
                 new_paid = val
            elif isinstance(val, (int, str)):
                 s = str(val).strip().lower()
                 if s in ("1", "true", "sí", "si", "on"): new_paid = True
                 elif s in ("0", "false", "no", "off"): new_paid = False
            updates["paid"] = 1 if new_paid else 0

    # Apply updates
    res_crud.update_reservation(db, res_id, **updates)
    
    # Sync Students
    user_ids = raw.get("user_ids")
    if user_ids is None and "student_ids" in raw:
        user_ids = raw.get("student_ids")
        
    if user_ids is not None and isinstance(user_ids, list):
        try:
            valid_ids = [int(uid) for uid in user_ids]
            res_crud.sync_reservation_students(db, res_id, valid_ids)
        except ValueError:
            pass 
            
    # Financial Logic
    effective_price_cents = updates.get("price_cents", reservation.price_cents)
    do_charge_global = (not prev_paid) and new_paid
    
    per_student_cents = {}
    try:
        psc = raw.get("per_student_cents") or {}
        if isinstance(psc, dict):
            for k, v in psc.items():
                try:
                    kk = int(k); vv = int(v or 0)
                    per_student_cents[kk] = max(0, vv)
                except: continue
    except: pass
    
    student_payments = {}
    try:
        sp = payload.student_payments or raw.get("student_payments") or {}
        if isinstance(sp, dict):
            student_payments = {int(k): bool(v) for k, v in sp.items()}
    except: pass

    current_students = res_crud.get_reservation_students(db, res_id)
    students_to_charge = []
    
    for student in current_students:
        sid = student["id"]
        is_paid_already = bool(student.get("paid", False))
        should_be_paid = student_payments.get(sid, False)
        if do_charge_global:
            should_be_paid = True
        if should_be_paid and not is_paid_already:
            students_to_charge.append(sid)
            res_crud.update_student_payment_status(db, res_id, sid, 1)

    if students_to_charge:
         now_txt = dt.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
         date_txt = updates.get("date", reservation.date)
         time_txt = updates.get("time", reservation.time) or "00:00"
         concept_base = f"Reserva {date_txt} {time_txt}"
         
         for sid in students_to_charge:
             share = per_student_cents.get(sid, effective_price_cents)
             share_eur = round(share / 100.0, 2)
             
             # Update Balance
             f = auth_crud.get_user_by_id(db, sid).finance
             current_bal = f.matricula_eur if f else 0.0
             new_bal = round(current_bal - share_eur, 2)
             auth_crud.update_user_finance(db, sid, matricula_eur=new_bal)
             
             if share > 0:
                 res_crud.create_expense(
                     db,
                     date=date_txt,
                     amount_cents=share,
                     category="reserva",
                     concept=concept_base,
                     user_id=sid,
                     reservation_id=created_reservation_id or res_id
                 )

    return {"ok": True, "id": res_id}


@router.delete("/api/reservations/{res_id}", response_model=dict)
async def api_reservations_delete(request: Request, res_id: int, db: Session = Depends(get_db)):
    require_docente_or_admin(request)
    
    # 1. Check if reservation exists
    reservation = res_crud.get_reservation_by_id(db, res_id)
    if not reservation:
         raise HTTPException(status_code=404, detail="Reserva no encontrada")

    # 2. Find associated expenses (fees paid)
    expenses = res_crud.get_expenses_by_reservation(db, res_id)
    
    # 3. Refund users
    for expense in expenses:
        if expense.user_id:
             user = auth_crud.get_user_by_id(db, expense.user_id)
             if user and user.finance:
                 current_bal = user.finance.matricula_eur
                 amount_eur = round(expense.amount_cents / 100.0, 2)
                 
                 # Refund: Add back to balance (since balance decrements on payment, we increment on refund? 
                 # Wait, usually "balance" in this context seems to be "credit available" or "amount paid"?
                 # Let's check api_reservations_create logic:
                 # new_bal = round(current_bal - share_eur, 2)
                 # So yes, it subtracts from balance. To refund, we must ADD to balance.
                 
                 new_bal = round(current_bal + amount_eur, 2)
                 auth_crud.update_user_finance(db, expense.user_id, matricula_eur=new_bal)

    # 4. Proceed to delete
    res_crud.delete_reservation(db, res_id)
    return {"ok": True}

# --- Quick User Creation (from reservation modal) ---

@router.post("/api/users/quick", response_model=dict)
async def api_users_quick_create(request: Request, db: Session = Depends(get_db)):
    """Create a simplified alumno user on-the-fly from the reservation modal."""
    require_docente_or_admin(request)
    
    try:
        data = await request.json()
    except Exception:
        raise HTTPException(status_code=400, detail="JSON inválido.")
    
    first_name = (data.get("first_name") or "").strip()
    last_name = (data.get("last_name") or "").strip()
    email = (data.get("email") or "").strip().lower()
    
    if not first_name or len(first_name) < 2:
        raise HTTPException(status_code=400, detail="El nombre es obligatorio (mínimo 2 caracteres).")
    if not last_name or len(last_name) < 2:
        raise HTTPException(status_code=400, detail="Los apellidos son obligatorios (mínimo 2 caracteres).")
    if not email or "@" not in email:
        raise HTTPException(status_code=400, detail="El email es obligatorio y debe ser válido.")
    
    # Check if user with that email already exists
    existing = auth_crud.get_user_by_email(db, email)
    if existing:
        raise HTTPException(status_code=409, detail="Ya existe un usuario con ese email.")
    
    # Generate random password (user won't need it, this is a quick-created user)
    random_password = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(12))
    
    name = f"{first_name} {last_name}"
    
    user_id = auth_crud.create_user(
        db,
        name=name,
        email=email,
        password=random_password,
        role="alumno",
        first_name=first_name,
        last_name=last_name
    )
    
    # Mark as temporary user
    user_obj = db.query(auth_crud.User).filter(auth_crud.User.id == user_id).first()
    if user_obj:
        user_obj.is_temporary = 1
        db.commit()
    
    return {
        "ok": True,
        "user": {
            "id": user_id,
            "first_name": first_name,
            "last_name": last_name,
            "email": email,
            "name": name
        }
    }


# --- Expenses ---

@router.get("/api/expenses", response_model=List[dict])
async def api_expenses_list(
    request: Request, 
    date_from: Optional[str] = None, 
    date_to: Optional[str] = None, 
    user_id: Optional[int] = None,
    db: Session = Depends(get_db)
):
    require_admin(request)
    expenses = res_crud.get_expenses(db, date_from, date_to, user_id)
    return expenses

@router.post("/api/expenses", response_model=dict)
async def api_expenses_create(request: Request, payload: ExpenseIn, db: Session = Depends(get_db)):
    require_admin(request)
    try:
        dt.datetime.strptime(payload.date, "%Y-%m-%d")
    except Exception:
        raise HTTPException(status_code=400, detail="Fecha inválida (YYYY-MM-DD).")
    
    gid = res_crud.create_expense(
        db=db,
        date=payload.date.strip(),
        amount_cents=int(payload.amount_cents or 0),
        category=(payload.category or "").strip() or None,
        concept=(payload.concept or "").strip() or None,
        notes=(payload.notes or "").strip() or None,
        user_id=int(payload.user_id) if payload.user_id is not None else None,
        reservation_id=int(payload.reservation_id) if payload.reservation_id else None
    )
    return {"ok": True, "id": gid}

@router.delete("/api/expenses/{gid}", response_model=dict)
async def api_expenses_delete(request: Request, gid: int, db: Session = Depends(get_db)):
    require_admin(request)
    res_crud.delete_expense(db, gid)
    return {"ok": True}
