"""
Physicians Celery Tasks

Background tasks for:
- Processing doctor rating import jobs
- Monthly aggregation of ratings
- Ranking updates
"""

import logging
from typing import Dict

from celery import shared_task
from django.utils import timezone

from apps.organizations.models import Hospital

from .adapter import DoctorRatingAdapter
from .models import DoctorRatingImportJob, PhysicianIndividualRating, PhysicianMonthlyRating

logger = logging.getLogger(__name__)


@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def process_doctor_rating_job(self, job_id: str):
    """
    Process a doctor rating import job in the background.

    This task is called when a bulk import is queued (from API or CSV upload).
    """
    try:
        job = DoctorRatingImportJob.objects.get(id=job_id)
    except DoctorRatingImportJob.DoesNotExist:
        logger.error(f"Doctor rating import job {job_id} not found")
        return {"error": "Job not found"}

    try:
        # Update job status
        job.status = DoctorRatingImportJob.JobStatus.PROCESSING
        job.started_at = timezone.now()
        job.save()

        logger.info(f"Starting doctor rating import job {job_id}: {job.total_records} records")

        # Get raw data
        records = job.raw_data
        hospital = job.hospital

        # Process through adapter
        results = DoctorRatingAdapter.process_bulk_ratings(records=records, hospital=hospital, job=job)

        logger.info(
            f"Completed doctor rating import job {job_id}: {results['success']} success, {results['failed']} failed"
        )

        return {
            "job_id": job_id,
            "total": results["total"],
            "success": results["success"],
            "failed": results["failed"],
            "skipped": results["skipped"],
            "staff_matched": results["staff_matched"],
        }

    except Exception as exc:
        logger.error(f"Error processing doctor rating job {job_id}: {str(exc)}", exc_info=True)

        # Update job status
        job.status = DoctorRatingImportJob.JobStatus.FAILED
        job.error_message = str(exc)
        job.completed_at = timezone.now()
        job.save()

        # Retry
        raise self.retry(exc=exc)


@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def aggregate_monthly_ratings_task(self, year: int, month: int, hospital_id: str = None):
    """
    Aggregate individual ratings into monthly summaries.

    Args:
        year: Year to aggregate
        month: Month to aggregate (1-12)
        hospital_id: Optional hospital ID to filter by
    """
    try:
        logger.info(f"Starting monthly aggregation for {year}-{month:02d}")

        hospital = None
        if hospital_id:
            try:
                hospital = Hospital.objects.get(id=hospital_id)
            except Hospital.DoesNotExist:
                logger.error(f"Hospital {hospital_id} not found")
                return {"error": "Hospital not found"}

        # Run aggregation
        results = DoctorRatingAdapter.aggregate_monthly_ratings(year=year, month=month, hospital=hospital)

        logger.info(
            f"Completed monthly aggregation for {year}-{month:02d}: {results['aggregated']} physicians aggregated"
        )

        # Calculate rankings after aggregation
        if hospital:
            update_hospital_rankings.delay(year, month, hospital_id)
        else:
            # Update rankings for all hospitals
            for h in Hospital.objects.filter(status="active"):
                update_hospital_rankings.delay(year, month, str(h.id))

        return {
            "year": year,
            "month": month,
            "hospital_id": hospital_id,
            "aggregated": results["aggregated"],
            "errors": len(results["errors"]),
        }

    except Exception as exc:
        logger.error(f"Error aggregating monthly ratings: {str(exc)}", exc_info=True)
        raise self.retry(exc=exc)


@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def update_hospital_rankings(self, year: int, month: int, hospital_id: str):
    """
    Update hospital and department rankings for physicians.

    This should be called after monthly aggregation is complete.
    """
    try:
        from django.db.models import Window, F
        from django.db.models.functions import RowNumber

        hospital = Hospital.objects.get(id=hospital_id)

        logger.info(f"Updating rankings for {hospital.name} - {year}-{month:02d}")

        # Get all ratings for this hospital and period
        ratings = PhysicianMonthlyRating.objects.filter(
            staff__hospital=hospital, year=year, month=month
        ).select_related("staff", "staff__department")

        # Update hospital rankings (order by average_rating desc)
        hospital_rankings = list(ratings.order_by("-average_rating"))
        for rank, rating in enumerate(hospital_rankings, start=1):
            rating.hospital_rank = rank
            rating.save(update_fields=["hospital_rank"])

        # Update department rankings
        from apps.organizations.models import Department

        departments = Department.objects.filter(hospital=hospital)

        for dept in departments:
            dept_ratings = ratings.filter(staff__department=dept).order_by("-average_rating")
            for rank, rating in enumerate(dept_ratings, start=1):
                rating.department_rank = rank
                rating.save(update_fields=["department_rank"])

        logger.info(f"Updated rankings for {hospital.name}: {len(hospital_rankings)} physicians ranked")

        return {
            "hospital_id": hospital_id,
            "hospital_name": hospital.name,
            "year": year,
            "month": month,
            "total_ranked": len(hospital_rankings),
        }

    except Exception as exc:
        logger.error(f"Error updating rankings: {str(exc)}", exc_info=True)
        raise self.retry(exc=exc)


@shared_task
def auto_aggregate_daily():
    """
    Daily task to automatically aggregate unaggregated ratings.

    This task should be scheduled to run daily to keep monthly ratings up-to-date.
    """
    try:
        logger.info("Starting daily auto-aggregation of doctor ratings")

        # Find months with unaggregated ratings
        unaggregated = (
            PhysicianIndividualRating.objects.filter(is_aggregated=False)
            .values("rating_date__year", "rating_date__month")
            .distinct()
        )

        aggregated_count = 0
        for item in unaggregated:
            year = item["rating_date__year"]
            month = item["rating_date__month"]

            # Aggregate for each hospital separately
            hospitals_with_ratings = (
                PhysicianIndividualRating.objects.filter(
                    is_aggregated=False, rating_date__year=year, rating_date__month=month
                )
                .values_list("hospital", flat=True)
                .distinct()
            )

            for hospital_id in hospitals_with_ratings:
                results = DoctorRatingAdapter.aggregate_monthly_ratings(year=year, month=month, hospital_id=hospital_id)
                aggregated_count += results["aggregated"]

        logger.info(f"Daily auto-aggregation complete: {aggregated_count} physicians updated")

        return {"aggregated_count": aggregated_count}

    except Exception as e:
        logger.error(f"Error in daily auto-aggregation: {str(e)}", exc_info=True)
        return {"error": str(e)}


@shared_task
def cleanup_old_import_jobs(days: int = 30):
    """
    Clean up old completed import jobs and their raw data.

    Args:
        days: Delete jobs older than this many days
    """
    from datetime import timedelta

    cutoff_date = timezone.now() - timedelta(days=days)

    old_jobs = DoctorRatingImportJob.objects.filter(
        created_at__lt=cutoff_date,
        status__in=[DoctorRatingImportJob.JobStatus.COMPLETED, DoctorRatingImportJob.JobStatus.FAILED],
    )

    count = old_jobs.count()

    # Clear raw data first to save space
    for job in old_jobs:
        if job.raw_data:
            job.raw_data = []
            job.save(update_fields=["raw_data"])

    logger.info(f"Cleaned up {count} old doctor rating import jobs")

    return {"cleaned_count": count}


def _fetch_and_process_his_doctor_ratings(job_id: str, from_date_iso: str, to_date_iso: str) -> Dict:
    """
    Core logic to fetch and process HIS doctor ratings.

    Can be called synchronously (from a view) or wrapped in a Celery task.
    """
    from datetime import datetime

    from apps.integrations.services.his_client import HISClient

    try:
        job = DoctorRatingImportJob.objects.get(id=job_id)
    except DoctorRatingImportJob.DoesNotExist:
        logger.error(f"Doctor rating import job {job_id} not found")
        return {"error": "Job not found"}

    try:
        from_date = datetime.fromisoformat(from_date_iso)
        to_date = datetime.fromisoformat(to_date_iso)
        if to_date.hour == 0 and to_date.minute == 0 and to_date.second == 0:
            to_date = to_date.replace(hour=23, minute=59, second=59)

        date_label = f"{from_date_iso} to {to_date_iso}"
        logger.info(f"Starting HIS doctor rating fetch for {date_label}")

        job.status = DoctorRatingImportJob.JobStatus.PROCESSING
        job.started_at = timezone.now()
        job.save()

        client = HISClient()
        his_data = client.fetch_doctor_ratings(from_date, to_date)

        if not his_data:
            job.status = DoctorRatingImportJob.JobStatus.FAILED
            job.error_message = "Failed to fetch data from HIS API"
            job.completed_at = timezone.now()
            job.save()
            logger.error("Failed to fetch data from HIS API")
            return {"success": False, "error": "Failed to fetch data from HIS API", "date_range": date_label}

        if his_data.get("Code") != 200:
            error_msg = his_data.get("Message", "Unknown error")
            job.status = DoctorRatingImportJob.JobStatus.FAILED
            job.error_message = f"HIS API error: {error_msg}"
            job.completed_at = timezone.now()
            job.save()
            logger.error(f"HIS API error: {error_msg}")
            return {"success": False, "error": f"HIS API error: {error_msg}", "date_range": date_label}

        ratings_list = his_data.get("FetchDoctorRatingMAPI1List", [])

        if not ratings_list:
            logger.info(
                f"HIS returned no ratings. Response keys: {list(his_data.keys())}, Code: {his_data.get('Code')}, Message: {his_data.get('Message')}"
            )
            logger.info(f"Full HIS response: {his_data}")
            job.status = DoctorRatingImportJob.JobStatus.COMPLETED
            job.total_records = 0
            job.processed_count = 0
            job.completed_at = timezone.now()
            job.results = {"stats": {"total": 0, "success": 0, "failed": 0, "duplicates": 0, "staff_matched": 0}}
            job.save()
            logger.info(f"No ratings found for {date_label}")
            return {
                "success": True,
                "date_range": date_label,
                "total_ratings": 0,
                "message": "No ratings found for this period",
            }

        logger.info(f"Fetched {len(ratings_list)} ratings from HIS for {date_label}")

        job.total_records = len(ratings_list)
        job.save()

        stats = {
            "total": len(ratings_list),
            "success": 0,
            "failed": 0,
            "duplicates": 0,
            "staff_matched": 0,
        }

        for idx, rating_data in enumerate(ratings_list, 1):
            try:
                hospital_name = rating_data.get("HospitalName", "")
                hospital = Hospital.objects.filter(name__iexact=hospital_name).first()

                if not hospital:
                    hospital = Hospital.objects.filter(name__icontains=hospital_name).first()

                if not hospital:
                    stats["failed"] += 1
                    logger.warning(f"Hospital not found: {hospital_name}")
                    continue

                result = DoctorRatingAdapter.process_his_rating_record(rating_data, hospital)

                if result["is_duplicate"]:
                    stats["duplicates"] += 1
                elif result["success"]:
                    stats["success"] += 1
                    if result["staff_matched"]:
                        stats["staff_matched"] += 1
                else:
                    stats["failed"] += 1
                    logger.warning(f"Failed to process rating: {result.get('message')}")

                if idx % 100 == 0:
                    job.processed_count = idx
                    job.success_count = stats["success"]
                    job.failed_count = stats["failed"]
                    job.save()
                    logger.info(f"Progress: {idx}/{stats['total']} processed")

            except Exception as e:
                stats["failed"] += 1
                logger.error(f"Error processing rating {idx}: {e}", exc_info=True)

        job.processed_count = stats["total"]
        job.success_count = stats["success"]
        job.failed_count = stats["failed"]
        job.completed_at = timezone.now()

        if stats["failed"] == 0:
            job.status = DoctorRatingImportJob.JobStatus.COMPLETED
        elif stats["success"] == 0:
            job.status = DoctorRatingImportJob.JobStatus.FAILED
        else:
            job.status = DoctorRatingImportJob.JobStatus.PARTIAL

        job.results = {"stats": stats}
        job.save()

        logger.info(
            f"Completed HIS doctor rating fetch for {date_label}: "
            f"{stats['success']} success, {stats['failed']} failed, {stats['duplicates']} duplicates"
        )

        return {
            "success": True,
            "date_range": date_label,
            "total_ratings": stats["total"],
            "success_count": stats["success"],
            "failed_count": stats["failed"],
            "duplicate_count": stats["duplicates"],
            "staff_matched_count": stats["staff_matched"],
        }

    except Exception as exc:
        logger.error(f"Error in HIS doctor rating fetch: {exc}", exc_info=True)
        try:
            job.status = DoctorRatingImportJob.JobStatus.FAILED
            job.error_message = str(exc)
            job.completed_at = timezone.now()
            job.save()
        except Exception:
            pass
        raise


@shared_task(bind=True, max_retries=3, default_retry_delay=300)
def fetch_his_doctor_ratings(self, job_id: str, from_date_iso: str, to_date_iso: str):
    """
    Celery task wrapper for fetching and processing HIS doctor ratings.

    Used by the monthly scheduled task. For manual UI fetches, the view
    calls _fetch_and_process_his_doctor_ratings() directly.
    """
    try:
        result = _fetch_and_process_his_doctor_ratings(job_id, from_date_iso, to_date_iso)
        # Trigger aggregation for any newly imported (or previously pending) ratings
        if result.get("success"):
            auto_aggregate_daily.delay()
        return result
    except Exception as exc:
        raise self.retry(exc=exc)


@shared_task(bind=True, max_retries=3, default_retry_delay=300)
def fetch_his_doctor_ratings_monthly(self):
    """
    Monthly task to fetch doctor ratings from HIS API.

    Runs on the 1st of each month to fetch the previous month's ratings.
    Example: On March 1st, fetches all ratings from February 1-28/29.

    This task runs at 1:00 AM on the 1st of each month, before the
    aggregation task which runs at 2:00 AM.
    """
    from calendar import monthrange
    from datetime import datetime

    try:
        now = timezone.now()
        if now.month == 1:
            target_year = now.year - 1
            target_month = 12
        else:
            target_year = now.year
            target_month = now.month - 1

        month_label = f"{target_year}-{target_month:02d}"
        logger.info(f"Starting monthly HIS doctor rating fetch for {month_label}")

        from_date = datetime(target_year, target_month, 1)
        last_day = monthrange(target_year, target_month)[1]
        to_date = datetime(target_year, target_month, last_day)

        first_hospital = Hospital.objects.first()
        if not first_hospital:
            logger.error("No hospitals found")
            return {"success": False, "error": "No hospitals found"}

        job = DoctorRatingImportJob.objects.create(
            name=f"Monthly HIS Import - {month_label}",
            status=DoctorRatingImportJob.JobStatus.PENDING,
            source=DoctorRatingImportJob.JobSource.HIS_API,
            hospital=first_hospital,
        )

        fetch_his_doctor_ratings.delay(str(job.id), from_date.date().isoformat(), to_date.date().isoformat())

        return {"success": True, "job_id": str(job.id), "month": month_label}

    except Exception as exc:
        logger.error(f"Error in monthly HIS doctor rating fetch: {exc}", exc_info=True)
        raise self.retry(exc=exc)


@shared_task(bind=True, max_retries=3, default_retry_delay=300)
def fetch_his_doctor_ratings_daily(self):
    """
    Daily task to fetch doctor ratings from HIS API for yesterday.

    Query window: FromDate=yesterday 00:00:00, ToDate=yesterday 23:59:59
    e.g. FromDate=08-Apr-2026 00:00:00&ToDate=08-Apr-2026 23:59:59
    """
    from datetime import datetime, timedelta

    try:
        yesterday = timezone.now().date() - timedelta(days=1)

        from_date = datetime.combine(yesterday, datetime.min.time())
        to_date = datetime(yesterday.year, yesterday.month, yesterday.day, 23, 59, 59)

        date_label = f"{from_date.date().isoformat()} to {to_date.date().isoformat()}"
        logger.info(f"Starting daily HIS doctor rating fetch for {date_label}")

        first_hospital = Hospital.objects.first()
        if not first_hospital:
            logger.error("No hospitals found")
            return {"success": False, "error": "No hospitals found"}

        job = DoctorRatingImportJob.objects.create(
            name=f"Daily HIS Import - {yesterday.isoformat()}",
            status=DoctorRatingImportJob.JobStatus.PENDING,
            source=DoctorRatingImportJob.JobSource.HIS_API,
            hospital=first_hospital,
        )

        fetch_his_doctor_ratings.delay(str(job.id), from_date.isoformat(), to_date.isoformat())

        return {"success": True, "job_id": str(job.id), "date": yesterday.isoformat()}

    except Exception as exc:
        logger.error(f"Error in daily HIS doctor rating fetch: {exc}", exc_info=True)
        raise self.retry(exc=exc)
