"""Waitlist endpoints."""

from datetime import datetime
from typing import List, Optional

import logging
import re

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

from app.api.deps import get_db, get_current_admin
from app.models import (
    Waitlist,
    AdminUser,
    CorporatePartner,
    EducationalPartner,
    PartnerType,
)
from app.schemas.waitlist import (
    WaitlistCreate,
    WaitlistResponse,
    WaitlistPrefillResponse,
    SendEmailRequest,
    UpdateStatusRequest,
)
from app.schemas.common import SuccessResponse
from app.services.email_service import EmailService

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

# UI placeholder strings that must never be stored as a real partner name when
# no source_id was resolved (they are not institution/company names).
_GENERIC_PARTNER_SOURCE_LABELS = frozenset(
    {
        "educationalpartner",
        "corporatepartner",
        "educationalagency",
        "corporateagency",
        "eduagency",
    }
)


def _normalize_source_label(value: str) -> str:
    return re.sub(r"[^a-z0-9]+", "", (value or "").lower())


def _is_generic_partner_placeholder(name: Optional[str]) -> bool:
    if not name or not str(name).strip():
        return False
    return _normalize_source_label(name) in _GENERIC_PARTNER_SOURCE_LABELS


def entry_to_dict(entry: Waitlist) -> dict:
    """Convert a Waitlist model to a dictionary for response. Null source is treated as Personal."""
    source = entry.source or "personal"
    source_name = entry.source_name or ("Personal" if not entry.source else None)
    return {
        "id": str(entry.id),
        "fullName": entry.full_name,
        "gender": entry.gender,
        "email": entry.email,
        "phone": entry.phone,
        "whatsappPhone": entry.whatsapp_phone,
        "dob": entry.date_of_birth,
        "age": entry.age,
        "state": entry.state,
        "lga": entry.lga,
        "qualification": entry.qualification,
        # Source tracking (null source shown as Personal)
        "source": source,
        "sourceName": source_name,
        "sourceId": str(entry.source_id) if entry.source_id else None,
        # Engagement tracking
        "invitationSent": entry.invitation_sent or False,
        "invitationSentAt": entry.invitation_sent_at,
        "followupCount": entry.followup_count or 0,
        "lastFollowupAt": entry.last_followup_at,
        "hasApplied": entry.has_applied or False,
        "applicationId": str(entry.application_id) if entry.application_id else None,
        "status": entry.status or "waiting",
        # Timestamps
        "createdAt": entry.created_at,
        "updatedAt": entry.updated_at,
    }


@router.post("", response_model=SuccessResponse, status_code=status.HTTP_201_CREATED)
async def create_waitlist_entry(
    waitlist_data: WaitlistCreate,
    db: Session = Depends(get_db),
):
    """Create a new waitlist entry."""
    # Check for duplicate email or phone
    existing = db.query(Waitlist).filter(
        (Waitlist.email == waitlist_data.email) | (Waitlist.phone == waitlist_data.phone)
    ).first()
    
    if existing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="You have already joined the waitlist with this email or phone number.",
        )
    
    # Create waitlist entry
    # Convert empty string to None for optional fields
    whatsapp_phone = (
        waitlist_data.whatsappPhone
        if waitlist_data.whatsappPhone and waitlist_data.whatsappPhone.strip()
        else None
    )

    # Handle source tracking fields
    source_id = None
    partner_name = (waitlist_data.sourceName or "").strip() or None
    source_value = (waitlist_data.source or "").strip() or None

    # Drop generic UI labels so we don't persist "Educational Partner" / "Corporate Partner"
    # as if they were real agency names (and skip pointless name matching).
    if partner_name and _is_generic_partner_placeholder(partner_name):
        partner_name = None

    # First, try to normalize/validate a UUID-based sourceId if provided
    if waitlist_data.sourceId:
        try:
            from uuid import UUID as UUIDType

            source_id = UUIDType(waitlist_data.sourceId)
        except (ValueError, TypeError):
            # Invalid UUID, ignore and fall back to slug-based resolution
            source_id = None

    # Helper: enrich source/sourceName from a concrete partner record
    def enrich_from_partner(partner_obj, partner_type: PartnerType):
        nonlocal source_value, partner_name, source_id

        if not partner_obj:
            return

        source_id = partner_obj.id

        # Derive a canonical source value from partner type when missing
        if not source_value:
            if partner_type == PartnerType.CORPORATE:
                source_value = "corporate"
            elif partner_type == PartnerType.EDUCATIONAL:
                source_value = "edu"

        # Derive a human-readable sourceName when missing
        if not partner_name:
            if partner_type == PartnerType.CORPORATE:
                partner_name = partner_obj.company_name
            elif partner_type == PartnerType.EDUCATIONAL:
                partner_name = partner_obj.institution_name

    # If we have a UUID, try to find the corresponding partner record
    if source_id:
        corporate = (
            db.query(CorporatePartner)
            .filter(CorporatePartner.id == source_id)
            .first()
        )
        if corporate:
            enrich_from_partner(corporate, PartnerType.CORPORATE)
        else:
            educational = (
                db.query(EducationalPartner)
                .filter(EducationalPartner.id == source_id)
                .first()
            )
            if educational:
                enrich_from_partner(educational, PartnerType.EDUCATIONAL)

    # If we still don't have a partner id but we have a slug, try resolving by slug
    if not source_id and getattr(waitlist_data, "sourceSlug", None):
        slug = (waitlist_data.sourceSlug or "").strip()
        if slug:
            # Slug is typically stored in extra_data JSON; we attempt lookup
            corporate = (
                db.query(CorporatePartner)
                .filter(
                    CorporatePartner.extra_data.isnot(None),
                    CorporatePartner.extra_data["slug"].astext == slug,
                )
                .first()
            )
            if corporate:
                enrich_from_partner(corporate, PartnerType.CORPORATE)
            else:
                educational = (
                    db.query(EducationalPartner)
                    .filter(
                        EducationalPartner.extra_data.isnot(None),
                        EducationalPartner.extra_data["slug"].astext == slug,
                    )
                    .first()
                )
                if educational:
                    enrich_from_partner(educational, PartnerType.EDUCATIONAL)

    # If we *still* don't have a partner id but we have a sourceName,
    # try resolving by case-insensitive name match. This covers legacy
    # flows where frontend only sent source/sourceName without id/slug.
    if not source_id and partner_name:
        def _normalize_name(value: str) -> str:
            """Normalize names for comparison: lowercase and strip non-alphanumerics."""
            return re.sub(r"[^a-z0-9]+", "", (value or "").lower())

        normalized_target = _normalize_name(partner_name)
        # Prefer lookup based on declared source when possible
        searched = False
        if source_value in ("edu", "educational"):
            # Educational partner: compare on normalized institution_name
            searched = True
            for ep in db.query(EducationalPartner).all():
                if _normalize_name(ep.institution_name or "") == normalized_target:
                    enrich_from_partner(ep, PartnerType.EDUCATIONAL)
                    break
        elif source_value == "corporate":
            # Corporate partner: compare on normalized company_name
            searched = True
            for cp in db.query(CorporatePartner).all():
                if _normalize_name(cp.company_name or "") == normalized_target:
                    enrich_from_partner(cp, PartnerType.CORPORATE)
                    break

        # Fallback: scan both tables if source was empty/unknown
        if not source_id and not searched:
            for ep in db.query(EducationalPartner).all():
                if _normalize_name(ep.institution_name or "") == normalized_target:
                    enrich_from_partner(ep, PartnerType.EDUCATIONAL)
                    break
            if not source_id:
                for cp in db.query(CorporatePartner).all():
                    if _normalize_name(cp.company_name or "") == normalized_target:
                        enrich_from_partner(cp, PartnerType.CORPORATE)
                        break

    # Final defaults:
    if not source_value:
        source_value = "personal"

    if source_id:
        # Resolved partner: use canonical name from enrich_from_partner
        source_name_value = partner_name
    elif source_value == "personal":
        source_name_value = partner_name or "Personal"
    elif source_value in ("edu", "educational", "corporate"):
        # Partner channel but no row linked: only keep a concrete name (for ops),
        # never a generic placeholder (already stripped from partner_name above).
        if partner_name and not _is_generic_partner_placeholder(partner_name):
            source_name_value = partner_name
        else:
            source_name_value = None
    else:
        source_name_value = partner_name

    waitlist_entry = Waitlist(
        full_name=waitlist_data.fullName,
        gender=waitlist_data.gender,
        email=waitlist_data.email,
        phone=waitlist_data.phone,
        whatsapp_phone=whatsapp_phone,
        date_of_birth=waitlist_data.dob,
        age=waitlist_data.age if waitlist_data.age and waitlist_data.age.strip() else None,
        state=waitlist_data.state,
        lga=waitlist_data.lga,
        qualification=waitlist_data.qualification,
        source=source_value,
        source_name=source_name_value,
        source_id=source_id,
        status="waiting",
    )
    
    db.add(waitlist_entry)
    db.commit()
    db.refresh(waitlist_entry)
    
    # Send confirmation email (don't fail if email fails)
    try:
        email_result = EmailService.send_waitlist_confirmation_email(waitlist_entry)
        if email_result.get("success"):
            logger.info(f"Waitlist confirmation email sent to {waitlist_entry.email}")
        else:
            logger.warning(f"Failed to send waitlist confirmation email to {waitlist_entry.email}: {email_result.get('error')}")
    except Exception as e:
        logger.error(f"Error sending waitlist confirmation email: {str(e)}")
    
    return SuccessResponse(
        data=WaitlistResponse.model_validate(entry_to_dict(waitlist_entry)).model_dump(),
        message="Successfully joined the waitlist!",
    )


@router.get("", response_model=SuccessResponse)
async def get_waitlist_entries(
    db: Session = Depends(get_db),
    current_admin: AdminUser = Depends(get_current_admin),
):
    """Get all waitlist entries (admin only)."""
    entries = db.query(Waitlist).order_by(Waitlist.created_at.desc()).all()
    
    # Convert to response format
    entries_list = []
    for entry in entries:
        entries_list.append(WaitlistResponse.model_validate(entry_to_dict(entry)).model_dump())
    
    return SuccessResponse(
        data=entries_list,
        message=f"Retrieved {len(entries_list)} waitlist entries",
    )


@router.get("/ref/{ref_id}", response_model=SuccessResponse)
async def get_waitlist_by_ref(
    ref_id: str,
    db: Session = Depends(get_db),
):
    """Public: get waitlist entry by id for application form prefill. Returns only safe fields. No auth."""
    entry = db.query(Waitlist).filter(Waitlist.id == ref_id).first()
    if not entry:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Waitlist entry not found",
        )
    data = WaitlistPrefillResponse(
        id=str(entry.id),
        fullName=entry.full_name,
        gender=entry.gender,
        email=entry.email,
        phone=entry.phone,
        whatsappPhone=entry.whatsapp_phone,
        dob=entry.date_of_birth,
        age=entry.age,
        state=entry.state,
        lga=entry.lga,
        qualification=entry.qualification,
    ).model_dump()
    return SuccessResponse(data=data, message="Waitlist entry for prefill")


@router.get("/{entry_id}", response_model=SuccessResponse)
async def get_waitlist_entry(
    entry_id: str,
    db: Session = Depends(get_db),
    current_admin: AdminUser = Depends(get_current_admin),
):
    """Get a specific waitlist entry by ID (admin only)."""
    entry = db.query(Waitlist).filter(Waitlist.id == entry_id).first()
    
    if not entry:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Waitlist entry not found",
        )
    
    return SuccessResponse(
        data=WaitlistResponse.model_validate(entry_to_dict(entry)).model_dump(),
        message="Waitlist entry retrieved",
    )


@router.post("/{entry_id}/send-email", response_model=SuccessResponse)
async def send_waitlist_email(
    entry_id: str,
    request_data: SendEmailRequest,
    db: Session = Depends(get_db),
    current_admin: AdminUser = Depends(get_current_admin),
):
    """Send invitation or follow-up email to a waitlist entry (admin only)."""
    entry = db.query(Waitlist).filter(Waitlist.id == entry_id).first()
    
    if not entry:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Waitlist entry not found",
        )
    
    # Check if already applied
    if entry.has_applied:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot send email to applicant who has already applied",
        )
    
    is_followup = request_data.isFollowup
    
    # Send actual email using Resend
    if is_followup:
        # It's a follow-up email
        if not entry.invitation_sent:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Cannot send follow-up before initial invitation",
            )
        
        new_followup_count = (entry.followup_count or 0) + 1
        
        # Send follow-up email
        email_result = EmailService.send_waitlist_followup_email(entry, new_followup_count)
        
        if not email_result.get("success"):
            logger.error(f"Failed to send follow-up email to {entry.email}: {email_result.get('error')}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"Failed to send email: {email_result.get('error', 'Unknown error')}",
            )
        
        entry.followup_count = new_followup_count
        entry.last_followup_at = datetime.utcnow()
        # Set status to "invited" if not already "converted" (don't downgrade converted status)
        if entry.status != "converted":
            entry.status = "invited"
        email_type = "follow-up"
        
    else:
        # It's the initial invitation
        email_result = EmailService.send_waitlist_invitation_email(entry)
        
        if not email_result.get("success"):
            logger.error(f"Failed to send invitation email to {entry.email}: {email_result.get('error')}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"Failed to send email: {email_result.get('error', 'Unknown error')}",
            )
        
        entry.invitation_sent = True
        entry.invitation_sent_at = datetime.utcnow()
        entry.status = "invited"
        email_type = "invitation"
    
    entry.updated_at = datetime.utcnow()
    db.commit()
    db.refresh(entry)
    
    logger.info(f"[EMAIL] Successfully sent {email_type} email to {entry.email} ({entry.full_name})")
    
    return SuccessResponse(
        data=WaitlistResponse.model_validate(entry_to_dict(entry)).model_dump(),
        message=f"Successfully sent {email_type} email to {entry.full_name}",
    )


@router.put("/{entry_id}/status", response_model=SuccessResponse)
async def update_waitlist_status(
    entry_id: str,
    request_data: UpdateStatusRequest,
    db: Session = Depends(get_db),
    current_admin: AdminUser = Depends(get_current_admin),
):
    """Update the status of a waitlist entry (admin only)."""
    entry = db.query(Waitlist).filter(Waitlist.id == entry_id).first()
    
    if not entry:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Waitlist entry not found",
        )
    
    new_status = request_data.status
    
    # Update status based on the request
    entry.status = new_status
    
    if new_status == "converted":
        entry.has_applied = True
    
    entry.updated_at = datetime.utcnow()
    db.commit()
    db.refresh(entry)
    
    return SuccessResponse(
        data=WaitlistResponse.model_validate(entry_to_dict(entry)).model_dump(),
        message=f"Status updated to {new_status}",
    )


@router.put("/{entry_id}/mark-applied", response_model=SuccessResponse)
async def mark_as_applied(
    entry_id: str,
    application_id: str = None,
    db: Session = Depends(get_db),
    current_admin: AdminUser = Depends(get_current_admin),
):
    """Mark a waitlist entry as having applied (admin only)."""
    entry = db.query(Waitlist).filter(Waitlist.id == entry_id).first()
    
    if not entry:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Waitlist entry not found",
        )
    
    entry.has_applied = True
    entry.status = "converted"
    if application_id:
        entry.application_id = application_id
    entry.updated_at = datetime.utcnow()
    
    db.commit()
    db.refresh(entry)
    
    return SuccessResponse(
        data=WaitlistResponse.model_validate(entry_to_dict(entry)).model_dump(),
        message="Marked as applied successfully",
    )


@router.delete("/{entry_id}", response_model=SuccessResponse)
async def delete_waitlist_entry(
    entry_id: str,
    db: Session = Depends(get_db),
    current_admin: AdminUser = Depends(get_current_admin),
):
    """Delete a waitlist entry (admin only)."""
    entry = db.query(Waitlist).filter(Waitlist.id == entry_id).first()
    if not entry:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Waitlist entry not found",
        )
    db.delete(entry)
    db.commit()
    return SuccessResponse(message="Waitlist entry deleted successfully")


@router.post("/bulk-delete", response_model=SuccessResponse)
async def bulk_delete_waitlist(
    ids: List[str] = Body(..., embed=True),
    db: Session = Depends(get_db),
    current_admin: AdminUser = Depends(get_current_admin),
):
    """Delete multiple waitlist entries (admin only)."""
    deleted = 0
    for entry_id in ids:
        entry = db.query(Waitlist).filter(Waitlist.id == entry_id).first()
        if entry:
            db.delete(entry)
            deleted += 1
    db.commit()
    return SuccessResponse(data={"deleted": deleted}, message=f"Deleted {deleted} waitlist entry(ies)")
