"""Service for application management."""

from datetime import datetime
from typing import List, Optional
from uuid import UUID

from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session

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, ApplicationStats
from app.services.matriculation_service import MatriculationService


class ApplicationService:
    """Service for managing applications."""

    @staticmethod
    def generate_application_id(db: Session) -> str:
        """Generate a unique application ID in format APP0001, APP0002, etc."""
        # Get the highest existing application ID
        last_app = db.query(Application).order_by(Application.application_id.desc()).first()
        
        if last_app and last_app.application_id.startswith("APP"):
            try:
                # Extract the number part
                last_num = int(last_app.application_id[3:])
                next_num = last_num + 1
            except ValueError:
                next_num = 1
        else:
            next_num = 1
        
        # Format: APP0001, APP0002, etc.
        return f"APP{next_num:04d}"

    @staticmethod
    def create_application(db: Session, application_data: ApplicationCreate) -> Application:
        """Create a new application."""
        # Check for duplicate application by email or phone
        app_dict = application_data.model_dump()
        existing_app = db.query(Application).filter(
            or_(
                Application.email == app_dict["email"],
                Application.phone == app_dict["phone"]
            )
        ).first()
        
        if existing_app:
            raise ValueError(
                f"You have already submitted an application. "
                f"Your existing application ID is {existing_app.application_id}. "
                f"Please contact support if you need to make changes."
            )
        
        application_id = ApplicationService.generate_application_id(db)
        
        # Convert Pydantic model to dict, handling field name conversion
        
        # Map frontend field names to database field names
        db_app = Application(
            application_id=application_id,
            first_name=app_dict["firstName"],
            last_name=app_dict["lastName"],
            middle_name=app_dict.get("middleName"),
            email=app_dict["email"],
            phone=app_dict["phone"],
            date_of_birth=app_dict.get("dateOfBirth"),
            gender=app_dict.get("gender"),
            state_of_origin=app_dict["stateOfOrigin"],
            lga=app_dict["lga"],
            nin=app_dict.get("nin"),
            nin_verified=app_dict.get("ninVerified", False),
            waec_number=app_dict.get("waecNumber"),
            waec_year=app_dict.get("waecYear"),
            waec_verified=app_dict.get("waecVerified", False),
            waec_verification_pending=app_dict.get("waecVerificationPending", False),
            jamb_reg_number=app_dict.get("jambRegNumber"),
            jamb_score=str(app_dict["jambScore"]) if app_dict.get("jambScore") else None,
            jamb_year=app_dict.get("jambYear"),
            programme=app_dict["programme"],
            current_occupation=app_dict.get("currentOccupation"),
            employer=app_dict.get("employer"),
            address=app_dict.get("address"),
            parent_guardian_name=app_dict.get("parentGuardianName"),
            parent_guardian_phone=app_dict.get("parentGuardianPhone"),
            status=ApplicationStatus.PENDING,
            date_applied=datetime.utcnow(),
            submitted_at=datetime.utcnow(),
        )
        
        db.add(db_app)
        db.commit()
        db.refresh(db_app)
        return db_app

    @staticmethod
    def get_applications(
        db: Session,
        status: Optional[ApplicationStatus] = None,
        search: Optional[str] = None,
        skip: int = 0,
        limit: int = 100,
    ) -> List[Application]:
        """Get applications with optional filtering and search."""
        query = db.query(Application)
        
        # Filter by status
        if status:
            query = query.filter(Application.status == status)
        
        # Search by name, email, or application ID
        if search:
            search_term = f"%{search.lower()}%"
            query = query.filter(
                or_(
                    func.lower(Application.first_name).like(search_term),
                    func.lower(Application.last_name).like(search_term),
                    func.lower(Application.email).like(search_term),
                    func.lower(Application.application_id).like(search_term),
                )
            )
        
        return query.order_by(Application.submitted_at.desc()).offset(skip).limit(limit).all()

    @staticmethod
    def get_application_by_id(db: Session, application_id: str) -> Optional[Application]:
        """Get a single application by ID (UUID or application_id)."""
        # Try UUID first
        try:
            uuid_id = UUID(application_id)
            return db.query(Application).filter(Application.id == uuid_id).first()
        except ValueError:
            # Try application_id format (APP0001)
            return db.query(Application).filter(Application.application_id == application_id).first()

    @staticmethod
    def update_status(
        db: Session,
        application: Application,
        status: ApplicationStatus,
        rejection_note: Optional[str] = None,
    ) -> Application:
        """Update application status and generate matric number if approved."""
        application.status = status
        application.updated_at = datetime.utcnow()
        
        if status == ApplicationStatus.APPROVED:
            # Generate matriculation number
            if not application.matric_number:
                application.matric_number = MatriculationService.generate_matric_number(db, application)
            application.admission_date = datetime.utcnow()
            application.rejection_note = None
            # Sync matric to student user if exists
            user = db.query(User).filter(User.application_id == application.application_id).first()
            if user:
                user.matric_number = application.matric_number
        elif status == ApplicationStatus.REJECTED:
            application.rejection_note = rejection_note
            application.rejection_date = datetime.utcnow()
            application.matric_number = None
            application.admission_date = None
            # Clear matric on linked student user if any
            user = db.query(User).filter(User.application_id == application.application_id).first()
            if user:
                user.matric_number = None
        db.commit()
        db.refresh(application)
        return application

    @staticmethod
    def delete_application(db: Session, application: Application) -> None:
        """Delete application and fully clean up related records (student user, waitlist, docs, payments, NIN verification)."""
        from app.models.document import Document
        from app.models.payment import Payment
        from app.models.nin_verification import NINVerification

        try:
            # Delete linked student users for this application
            db.query(User).filter(
                User.application_id == application.application_id,
                User.user_type == UserType.GLOBAL_UNDERGRADUATE,
            ).delete(synchronize_session="fetch")

            # Delete waitlist entries that were converted from this application
            db.query(Waitlist).filter(Waitlist.application_id == application.id).delete(synchronize_session="fetch")

            # Delete documents and payments tied to this application
            db.query(Document).filter(Document.application_id == application.id).delete(synchronize_session="fetch")
            db.query(Payment).filter(Payment.application_id == application.id).delete(synchronize_session="fetch")

            # Delete NIN verification record associated with this applicant's NIN (if any)
            if application.nin:
                db.query(NINVerification).filter(NINVerification.nin == application.nin).delete(
                    synchronize_session="fetch"
                )

            # Finally delete the application itself
            db.delete(application)
            db.commit()
        except Exception:
            db.rollback()
            raise

    @staticmethod
    def get_statistics(db: Session) -> ApplicationStats:
        """Get dashboard statistics."""
        total = db.query(Application).count()
        pending = db.query(Application).filter(Application.status == ApplicationStatus.PENDING).count()
        approved = db.query(Application).filter(Application.status == ApplicationStatus.APPROVED).count()
        rejected = db.query(Application).filter(Application.status == ApplicationStatus.REJECTED).count()
        
        return ApplicationStats(
            total=total,
            pending=pending,
            approved=approved,
            rejected=rejected,
        )
