from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Optional

from fastapi import HTTPException
from sqlalchemy.orm import Session

from src.apps.admin.models.admin_audit_log import AdminAuditLog
from src.apps.admin.models.scheduler_config import SchedulerConfig
from src.apps.admin.schemas.scheduler_schemas import (
    SchedulerDetail,
    SchedulerLogResponse,
    SchedulerSummary,
    TriggerSchedulerRequest,
    UpdateSchedulerConfigRequest,
)
from src.apps.admin.scheduler_crud import (
    create_scheduler_log,
    get_all_scheduler_configs,
    get_health_summary,
    get_last_run,
    get_last_run_by_status,
    get_scheduler_config,
    get_scheduler_log,
    get_scheduler_logs,
    get_success_rate_30d,
    get_total_runs_30d,
    update_scheduler_config as crud_update_scheduler_config,
)

logger = logging.getLogger(__name__)


def _build_summary(db: Session, config: SchedulerConfig) -> SchedulerSummary:
    last_run = get_last_run(db, config.task_name)
    last_success = get_last_run_by_status(db, config.task_name, "SUCCESS")
    last_failure = get_last_run_by_status(db, config.task_name, "FAILED")
    rate = get_success_rate_30d(db, config.task_name)
    return SchedulerSummary(
        task_name=config.task_name,
        display_name=config.display_name,
        schedule_expression=config.schedule_expression,
        is_enabled=config.is_enabled,
        supports_merchant_filter=config.supports_merchant_filter,
        alert_threshold_ms=config.alert_threshold_ms,
        last_run=SchedulerLogResponse.model_validate(last_run) if last_run else None,
        last_success_at=last_success.started_at if last_success else None,
        last_failure_at=last_failure.started_at if last_failure else None,
        success_rate_30d=rate,
    )


def get_scheduler_list(db: Session) -> list[SchedulerSummary]:
    configs = get_all_scheduler_configs(db)
    return [_build_summary(db, c) for c in configs]


def get_scheduler_detail(db: Session, task_name: str) -> SchedulerDetail:
    config = get_scheduler_config(db, task_name)
    if not config:
        raise HTTPException(status_code=404, detail=f"Scheduler '{task_name}' not found")

    summary = _build_summary(db, config)
    logs, _ = get_scheduler_logs(db, task_name=task_name, page=1, page_size=20)
    total_runs_30d = get_total_runs_30d(db, task_name)

    return SchedulerDetail(
        **summary.model_dump(),
        recent_logs=[SchedulerLogResponse.model_validate(lg) for lg in logs],
        log_retention_days=config.log_retention_days,
        description=config.description,
        total_runs_30d=total_runs_30d,
    )


def trigger_scheduler(
    db: Session,
    task_name: str,
    request: TriggerSchedulerRequest,
    admin_user,
) -> dict:
    from src.worker.celery_app import celery_app, MONITORED_TASKS

    config = get_scheduler_config(db, task_name)
    if not config:
        raise HTTPException(status_code=404, detail=f"Scheduler '{task_name}' not found")
    if not config.is_enabled:
        raise HTTPException(status_code=400, detail="Scheduler is currently paused/disabled")
    if request.merchant_ids and not config.supports_merchant_filter:
        raise HTTPException(
            status_code=400,
            detail="This scheduler does not support merchant-scoped runs",
        )
    if task_name not in MONITORED_TASKS:
        raise HTTPException(status_code=400, detail="Task is not in the monitored task registry")

    kwargs: dict = {}
    if request.merchant_ids:
        kwargs["merchant_ids"] = request.merchant_ids
    kwargs["_triggered_by_admin"] = True
    kwargs["_triggered_by_admin_id"] = admin_user.id

    # Step 1: Create audit log entry and flush (validate DB constraints before dispatch)
    AdminAuditLog.log(
        db=db,
        admin_user_id=admin_user.id,
        action="scheduler.trigger",
        target_type="scheduler_config",
        target_id=config.id,
        metadata={"task_name": task_name, "merchant_ids": request.merchant_ids},
    )
    db.flush()

    # Step 2: Only dispatch task after audit log is safely staged
    result = celery_app.send_task(task_name, kwargs=kwargs)

    db.commit()

    return {"celery_task_id": result.id, "task_name": task_name}


def pause_resume_scheduler(
    db: Session,
    task_name: str,
    is_enabled: bool,
    admin_user,
) -> SchedulerConfig:
    config = get_scheduler_config(db, task_name)
    if not config:
        raise HTTPException(status_code=404, detail=f"Scheduler '{task_name}' not found")

    config.is_enabled = is_enabled
    config.last_modified_by_admin_id = admin_user.id
    db.flush()

    action = "scheduler.resume" if is_enabled else "scheduler.pause"
    AdminAuditLog.log(
        db=db,
        admin_user_id=admin_user.id,
        action=action,
        target_type="scheduler",
        target_id=config.id,
        metadata={"task_name": task_name, "is_enabled": is_enabled},
    )
    db.commit()
    db.refresh(config)
    return config


def retry_scheduler_log(
    db: Session,
    log_id: int,
    admin_user,
) -> dict:
    from src.worker.celery_app import celery_app, MONITORED_TASKS

    log = get_scheduler_log(db, log_id)
    if not log:
        raise HTTPException(status_code=404, detail=f"Scheduler log #{log_id} not found")
    if log.run_status != "FAILED":
        raise HTTPException(
            status_code=400,
            detail=f"Log #{log_id} has status '{log.run_status}' — only FAILED runs can be retried",
        )
    if log.task_name not in MONITORED_TASKS:
        raise HTTPException(status_code=400, detail="Task is not in the monitored task registry")

    kwargs: dict = {"_parent_log_id": log_id}
    if log.merchant_ids:
        kwargs["merchant_ids"] = log.merchant_ids
    kwargs["_triggered_by_admin"] = True
    kwargs["_triggered_by_admin_id"] = admin_user.id

    config = get_scheduler_config(db, log.task_name)

    # Step 1: Create audit log entry and flush (validate DB constraints before dispatch)
    AdminAuditLog.log(
        db=db,
        admin_user_id=admin_user.id,
        action="scheduler.retry",
        target_type="scheduler_config",
        target_id=config.id if config else 0,
        metadata={"task_name": log.task_name, "parent_log_id": log_id},
    )
    db.flush()

    # Step 2: Only dispatch task after audit log is safely staged
    result = celery_app.send_task(log.task_name, kwargs=kwargs)

    db.commit()

    return {"celery_task_id": result.id, "task_name": log.task_name, "parent_log_id": log_id}


def update_scheduler_config(
    db: Session,
    task_name: str,
    request: UpdateSchedulerConfigRequest,
    admin_user,
) -> SchedulerConfig:
    config = get_scheduler_config(db, task_name)
    if not config:
        raise HTTPException(status_code=404, detail=f"Scheduler '{task_name}' not found")

    update_data = request.model_dump(exclude_none=True)
    config = crud_update_scheduler_config(db, task_name, update_data, admin_user.id)

    # Log pause/resume as distinct actions; remaining fields as config_update
    if "is_enabled" in update_data:
        action = "scheduler.resume" if update_data["is_enabled"] else "scheduler.pause"
        AdminAuditLog.log(
            db=db,
            admin_user_id=admin_user.id,
            action=action,
            target_type="scheduler_config",
            target_id=config.id,
            metadata={"task_name": task_name, "is_enabled": update_data["is_enabled"]},
        )
        remaining_updates = {k: v for k, v in update_data.items() if k != "is_enabled"}
        if remaining_updates:
            AdminAuditLog.log(
                db=db,
                admin_user_id=admin_user.id,
                action="scheduler.config_update",
                target_type="scheduler_config",
                target_id=config.id,
                metadata={"task_name": task_name, "updates": remaining_updates},
            )
    else:
        AdminAuditLog.log(
            db=db,
            admin_user_id=admin_user.id,
            action="scheduler.config_update",
            target_type="scheduler_config",
            target_id=config.id,
            metadata={"task_name": task_name, "updates": update_data},
        )

    db.commit()
    db.refresh(config)
    return config
