"""Application management endpoints."""

import base64
import re
from datetime import datetime, date
from typing import List, Optional
import logging

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from sqlalchemy import func

from fastapi import Body
from pydantic import BaseModel

from app.api.deps import get_current_admin, get_db
from app.api.v1.documents import upload_to_imagekit
from app.config import settings
from app.models.admin_user import AdminRole
from app.models.application import Application, ApplicationStatus
from app.models.user import User, UserType
from app.models.waitlist import Waitlist
from app.schemas.application import ApplicationCreate, ApplicationResponse, ApplicationStats, ApplicationUpdate
from app.schemas.common import ErrorResponse, SuccessResponse
from app.services.application_service import ApplicationService
from app.services.email_service import EmailService
from app.utils.security import get_password_hash

router = APIRouter(prefix="/applications", tags=["applications"])
logger = logging.getLogger(__name__)


class SendAdmissionLetterBody(BaseModel):
    """Required: frontend-generated admission letter PDF as base64. Server-side PDF generation is disabled."""
    pdf_base64: Optional[str] = None  # Optional for schema; endpoint returns 400 if missing


def _student_initial_password(first_name: str) -> str:
    """Generate initial student password: @<first 5 letters of firstname>#2026POU."""
    letters = re.sub(r"[^a-zA-Z]", "", (first_name or "").strip())
    segment = (letters[:5] or "Stud").capitalize()
    return f"@{segment}#2026POU"


def application_to_dict(application) -> dict:
    """Convert Application model to dict for ApplicationResponse schema."""
    return {
        "id": str(application.id),
        "application_id": application.application_id,
        "first_name": application.first_name,
        "last_name": application.last_name,
        "middle_name": application.middle_name,
        "email": application.email,
        "phone": application.phone,
        "date_of_birth": application.date_of_birth,
        "gender": application.gender,
        "state_of_origin": application.state_of_origin,
        "lga": application.lga,
        "nin": application.nin,
        "nin_verified": application.nin_verified or False,
        "waec_number": application.waec_number,
        "waec_year": application.waec_year,
        "waec_verified": application.waec_verified or False,
        "waec_verification_pending": application.waec_verification_pending or False,
        "jamb_reg_number": application.jamb_reg_number,
        "jamb_score": application.jamb_score,
        "jamb_year": application.jamb_year,
        "programme": application.programme,
        "current_occupation": application.current_occupation,
        "employer": application.employer,
        "address": application.address,
        "parent_guardian_name": application.parent_guardian_name,
        "parent_guardian_phone": application.parent_guardian_phone,
        "status": application.status,
        "rejection_note": application.rejection_note,
        "rejection_date": application.rejection_date,
        "matric_number": application.matric_number,
        "admission_date": application.admission_date,
        "date_applied": application.date_applied,
        "submitted_at": application.submitted_at,
        "updated_at": application.updated_at,
    }


@router.post("", response_model=SuccessResponse, status_code=status.HTTP_201_CREATED)
async def create_application(
    application_data: ApplicationCreate,
    db: Session = Depends(get_db),
):
    """Submit a new application."""
    try:
        application = ApplicationService.create_application(db, application_data)

        # Create student user linked to application_id (e.g. APP0001); skip if email already has a user
        try:
            existing_user = db.query(User).filter(User.email == application.email).first()
            if not existing_user:
                initial_password = _student_initial_password(application.first_name)
                student_user = User(
                    email=application.email,
                    password_hash=get_password_hash(initial_password),
                    user_type=UserType.GLOBAL_UNDERGRADUATE,
                    application_id=application.application_id,
                    matric_number=None,
                    is_active=True,
                )
                db.add(student_user)
                db.commit()
                db.refresh(student_user)
                logger.info(f"Created student user for application {application.application_id} (email: {application.email})")
            else:
                logger.info(f"User already exists for {application.email}; skipping user creation for application {application.application_id}")
        except Exception as user_error:
            logger.error(f"Error creating student user: {str(user_error)}", exc_info=True)
            # Don't fail application; user can be created later by admin

        # Waitlist: if ref provided, update that entry only; else match/insert
        try:
            waitlist_ref = getattr(application_data, "waitlistRef", None) or getattr(
                application_data, "waitlist_ref", None
            )
            full_name_app = f"{application.first_name or ''} {application.last_name or ''}".strip() or application.email

            waitlist_entry = None
            if waitlist_ref:
                # User came from invitation link: update the specific waitlist entry
                waitlist_entry = db.query(Waitlist).filter(Waitlist.id == waitlist_ref).first()
                if waitlist_entry:
                    waitlist_entry.full_name = full_name_app
                    waitlist_entry.has_applied = True
                    waitlist_entry.status = "converted"
                    waitlist_entry.application_id = application.id
                    waitlist_entry.updated_at = datetime.utcnow()
                    if application.gender:
                        waitlist_entry.gender = application.gender
                    if application.date_of_birth:
                        waitlist_entry.date_of_birth = application.date_of_birth
                    waitlist_entry.state = application.state_of_origin or waitlist_entry.state
                    waitlist_entry.lga = application.lga or waitlist_entry.lga
                    waitlist_entry.qualification = application.programme or waitlist_entry.qualification
                    db.commit()
                    logger.info(
                        f"Updated waitlist entry {waitlist_entry.id} (ref) to 'converted' "
                        f"for application {application.application_id}"
                    )
                else:
                    logger.warning(f"Waitlist ref {waitlist_ref} not found; will not create new waitlist entry")

            if not waitlist_entry and not waitlist_ref:
                # No ref or ref not found: match by email only
                waitlist_entry = db.query(Waitlist).filter(
                    func.lower(Waitlist.email) == application.email.lower()
                ).first()

            elif waitlist_entry and not waitlist_ref:
                # Matched by email+phone+name (no ref)
                waitlist_entry.full_name = full_name_app
                waitlist_entry.has_applied = True
                waitlist_entry.status = "converted"
                waitlist_entry.application_id = application.id
                waitlist_entry.updated_at = datetime.utcnow()
                if application.gender:
                    waitlist_entry.gender = application.gender
                waitlist_entry.state = application.state_of_origin or waitlist_entry.state
                waitlist_entry.lga = application.lga or waitlist_entry.lga
                waitlist_entry.qualification = application.programme or waitlist_entry.qualification
                db.commit()
                logger.info(f"Updated waitlist entry {waitlist_entry.id} to 'converted' for application {application.application_id}")
            elif not waitlist_entry and not waitlist_ref:
                # Only insert new waitlist when ref is empty (direct apply, no invite link)
                dob = application.date_of_birth if application.date_of_birth else date(2000, 1, 1)
                new_entry = Waitlist(
                    full_name=full_name_app,
                    gender=application.gender or "Other",
                    email=application.email,
                    phone=application.phone,
                    date_of_birth=dob,
                    state=application.state_of_origin,
                    lga=application.lga,
                    qualification=application.programme or "N/A",
                    source="personal",
                    source_name="Personal",
                    source_id=None,
                    has_applied=True,
                    application_id=application.id,
                    status="converted",
                )
                db.add(new_entry)
                db.commit()
                logger.info(f"Inserted new waitlist entry for application {application.application_id} (no prior match)")
        except Exception as waitlist_error:
            logger.error(f"Error syncing waitlist: {str(waitlist_error)}", exc_info=True)

        # Send confirmation email (optional - don't fail if email service unavailable)
        try:
            logger.info(f"Attempting to send confirmation email to {application.email} for application {application.application_id}")
            email_result = EmailService.send_confirmation_email(application)
            if not email_result.get("success"):
                error_msg = email_result.get("error", "Unknown error")
                logger.error(f"Failed to send confirmation email to {application.email}: {error_msg}")
                if not settings.resend_key:
                    try:
                        print("[WARNING] Resend API key is not configured! Please set RESEND_KEY in .env file")
                    except (UnicodeEncodeError, Exception):
                        pass
            else:
                logger.info(f"Confirmation email sent successfully to {application.email}")
        except Exception as email_error:
            logger.error(f"Exception while sending confirmation email: {str(email_error)}", exc_info=True)

        return SuccessResponse(
            data=ApplicationResponse.model_validate(application_to_dict(application)).model_dump(),
            message=f"Application submitted successfully. Your application ID is {application.application_id}",
        )
    except ValueError as e:
        # Handle duplicate application or validation errors
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e),
        )
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to create application: {str(e)}",
        )


@router.get("", response_model=SuccessResponse)
async def get_applications(
    status: Optional[str] = Query(None, description="Filter by status: pending, approved, rejected"),
    search: Optional[str] = Query(None, description="Search by name, email, or application ID"),
    skip: int = Query(0, ge=0),
    limit: int = Query(100, ge=1, le=1000),
    db: Session = Depends(get_db),
    current_admin = Depends(get_current_admin),
):
    """Get all applications (admin only)."""
    from app.models.application import ApplicationStatus
    
    status_filter = None
    if status:
        try:
            status_filter = ApplicationStatus(status.lower())
        except ValueError:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Invalid status: {status}. Must be one of: pending, approved, rejected",
            )
    
    applications = ApplicationService.get_applications(
        db, status=status_filter, search=search, skip=skip, limit=limit
    )
    
    return SuccessResponse(
        data=[ApplicationResponse.model_validate(application_to_dict(app)).model_dump() for app in applications],
        message="Applications retrieved successfully",
    )


@router.get("/stats", response_model=SuccessResponse)
async def get_statistics(
    db: Session = Depends(get_db),
    current_admin = Depends(get_current_admin),
):
    """Get dashboard statistics (admin only)."""
    stats = ApplicationService.get_statistics(db)
    return SuccessResponse(
        data=stats.model_dump(),
        message="Statistics retrieved successfully",
    )


@router.post("/{application_id}/prepare-admission", response_model=SuccessResponse)
async def prepare_admission(
    application_id: str,
    db: Session = Depends(get_db),
    current_admin=Depends(get_current_admin),
):
    """
    Approve application, generate matric number, and return preview data for the admission letter.
    Does not send email. Use send-admission-letter to generate PDF and email.
    """
    application = ApplicationService.get_application_by_id(db, application_id)
    if not application:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Application not found",
        )
    if application.status != ApplicationStatus.APPROVED:
        application = ApplicationService.update_status(
            db, application, ApplicationStatus.APPROVED, None
        )
    temporary_password = _student_initial_password(application.first_name)
    app_dict = application_to_dict(application)
    data = {
        "matricNumber": application.matric_number,
        "admissionDate": (
            application.admission_date.isoformat()
            if application.admission_date
            else datetime.utcnow().isoformat()
        ),
        "email": application.email,
        "temporaryPassword": temporary_password,
        "firstName": application.first_name,
        "lastName": application.last_name,
        "programme": application.programme,
        "applicationId": application.application_id,
        "application": app_dict,
    }
    return SuccessResponse(data=data, message="Admission prepared for preview")


@router.post("/{application_id}/send-admission-letter", response_model=SuccessResponse)
async def send_admission_letter(
    application_id: str,
    body: Optional[SendAdmissionLetterBody] = Body(default=None),
    db: Session = Depends(get_db),
    current_admin=Depends(get_current_admin),
):
    """Send admission letter email with PDF attachment. Requires frontend-generated PDF (pdf_base64) so the design matches the official template."""
    application = ApplicationService.get_application_by_id(db, application_id)
    if not application:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Application not found",
        )
    if application.status != ApplicationStatus.APPROVED or not application.matric_number:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Application must be approved with a matric number. Use prepare-admission first.",
        )
    if not body or not body.pdf_base64:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Admission letter PDF must be generated from the admin dashboard (Issue Admission Letter) and sent as pdf_base64. Server-side generation is not used.",
        )
    try:
        pdf_bytes = base64.b64decode(body.pdf_base64)
    except Exception:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid pdf_base64 for admission letter attachment.",
        )
    file_name = f"admission_letter_{application.application_id}.pdf"
    try:
        pdf_url = await upload_to_imagekit(
            pdf_bytes, file_name, folder="admission-letters"
        )
        logger.info(f"Admission letter uploaded to ImageKit: {pdf_url}")
    except Exception as e:
        logger.warning(f"ImageKit upload failed, sending email without stored URL: {e}")
        pdf_url = None
    attachments = [
        {
            "filename": file_name,
            "content": base64.b64encode(pdf_bytes).decode("utf-8"),
        }
    ]
    temporary_password = _student_initial_password(application.first_name)
    login_url = f"{settings.frontend_base_url.rstrip('/')}/login"
    email_result = EmailService.send_admission_email(
        application,
        pdf_path=None,
        pdf_attachments=attachments,
        temporary_password=temporary_password,
        login_url=login_url,
    )
    if not email_result.get("success"):
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=email_result.get("error", "Failed to send admission email"),
        )
    return SuccessResponse(
        data={"email": application.email, "pdfUrl": pdf_url},
        message=f"Admission letter sent to {application.email}",
    )


@router.get("/{application_id}", response_model=SuccessResponse)
async def get_application(
    application_id: str,
    db: Session = Depends(get_db),
    current_admin = Depends(get_current_admin),
):
    """Get a single application by ID (admin only)."""
    application = ApplicationService.get_application_by_id(db, application_id)
    if not application:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Application not found",
        )
    
    return SuccessResponse(
        data=ApplicationResponse.model_validate(application_to_dict(application)).model_dump(),
        message="Application retrieved successfully",
    )


@router.put("/{application_id}/status", response_model=SuccessResponse)
async def update_application_status(
    application_id: str,
    update_data: ApplicationUpdate,
    db: Session = Depends(get_db),
    current_admin = Depends(get_current_admin),
):
    """Update application status (admin only)."""
    application = ApplicationService.get_application_by_id(db, application_id)
    if not application:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Application not found",
        )
    
    # Update status
    application = ApplicationService.update_status(
        db,
        application,
        update_data.status,
        update_data.rejectionNote,
    )
    
    # Send rejection email when status is rejected. Admission email is sent via send-admission-letter flow.
    try:
        from app.services.email_service import EmailService
        if update_data.status.value == "rejected":
            email_result = EmailService.send_rejection_email(application)
            if not email_result.get("success"):
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f"Failed to send rejection email: {email_result.get('error', 'Unknown error')}")
    except Exception as email_error:
        # Log but don't fail the status update if email service is unavailable
        import logging
        logger = logging.getLogger(__name__)
        logger.warning(f"Failed to send email: {str(email_error)}")
    
    return SuccessResponse(
        data=ApplicationResponse.model_validate(application_to_dict(application)).model_dump(),
        message=f"Application status updated to {update_data.status.value}",
    )


@router.delete("/{application_id}", response_model=SuccessResponse)
async def delete_application(
    application_id: str,
    db: Session = Depends(get_db),
    current_admin=Depends(get_current_admin),
):
    """Delete a single application (admin only). Cleans up related records."""
    application = ApplicationService.get_application_by_id(db, application_id)
    if not application:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Application not found",
        )
    ApplicationService.delete_application(db, application)
    return SuccessResponse(message="Application deleted successfully")


@router.post("/bulk-delete", response_model=SuccessResponse)
async def bulk_delete_applications(
    ids: List[str] = Body(..., embed=True),
    db: Session = Depends(get_db),
    current_admin=Depends(get_current_admin),
):
    """Delete multiple applications by ID (admin only)."""
    deleted = 0
    for app_id in ids:
        app = ApplicationService.get_application_by_id(db, app_id)
        if app:
            ApplicationService.delete_application(db, app)
            deleted += 1
    return SuccessResponse(data={"deleted": deleted}, message=f"Deleted {deleted} application(s)")
