"""Asyncio-based report scheduler — generates daily/weekly/monthly reports automatically.

All schedule times are interpreted in KST (Asia/Seoul, UTC+9).
Each period type (daily, weekly, monthly) has its own configurable hour/minute.
"""
from __future__ import annotations

import asyncio
import json
import logging
from datetime import datetime, timedelta, timezone

from src.report_generator import (
    generate_daily_report,
    generate_weekly_report,
    generate_monthly_report,
)
from src.tools.helpers import _env_bool, _env_int, compute_period_end
from src.tools.langfuse_fetch import (
    _build_langfuse_client,
    langfuse_settings_scope,
)
from src.user_store import (
    insert_user_report,
    list_active_users,
    list_user_langfuse_projects,
    get_user_langfuse_settings,
)

log = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# KST timezone (UTC+9, fixed offset — no DST)
# ---------------------------------------------------------------------------

KST = timezone(timedelta(hours=9))

# ---------------------------------------------------------------------------
# Configuration from environment
# ---------------------------------------------------------------------------
#
# REPORT_SCHEDULER_ENABLED          (default: true)
#
# Per-period schedule (all in KST):
#   REPORT_SCHEDULER_DAILY_HOUR     (default: 0)
#   REPORT_SCHEDULER_DAILY_MINUTE   (default: 0)
#   REPORT_SCHEDULER_WEEKLY_HOUR    (default: 0)
#   REPORT_SCHEDULER_WEEKLY_MINUTE  (default: 0)
#   REPORT_SCHEDULER_MONTHLY_HOUR   (default: 0)
#   REPORT_SCHEDULER_MONTHLY_MINUTE (default: 0)
# ---------------------------------------------------------------------------


def _daily_hour() -> int:
    """Return configured hour for daily report schedule (0-23)."""
    return _env_int("REPORT_SCHEDULER_DAILY_HOUR", default=0, minimum=0, maximum=23)


def _daily_minute() -> int:
    """Return configured minute for daily report schedule (0-59)."""
    return _env_int("REPORT_SCHEDULER_DAILY_MINUTE", default=0, minimum=0, maximum=59)


def _weekly_hour() -> int:
    """Return configured hour for weekly report schedule (0-23)."""
    return _env_int("REPORT_SCHEDULER_WEEKLY_HOUR", default=0, minimum=0, maximum=23)


def _weekly_minute() -> int:
    """Return configured minute for weekly report schedule (0-59)."""
    return _env_int("REPORT_SCHEDULER_WEEKLY_MINUTE", default=0, minimum=0, maximum=59)


def _monthly_hour() -> int:
    """Return configured hour for monthly report schedule (0-23)."""
    return _env_int("REPORT_SCHEDULER_MONTHLY_HOUR", default=0, minimum=0, maximum=23)


def _monthly_minute() -> int:
    """Return configured minute for monthly report schedule (0-59)."""
    return _env_int("REPORT_SCHEDULER_MONTHLY_MINUTE", default=0, minimum=0, maximum=59)


# ---------------------------------------------------------------------------
# In-memory duplicate guards
# ---------------------------------------------------------------------------

_scheduler_task: asyncio.Task | None = None
_last_daily: str = ""
_last_weekly: str = ""
_last_monthly: str = ""

# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


async def start_scheduler() -> None:
    """Start the background scheduler task. Called from FastAPI lifespan."""
    global _scheduler_task

    if not _env_bool("REPORT_SCHEDULER_ENABLED", default=True):
        log.info("Report scheduler disabled (REPORT_SCHEDULER_ENABLED=false)")
        return

    log.info(
        "Report scheduler started [KST] "
        "(daily=%02d:%02d, weekly=Mon %02d:%02d, monthly=1st %02d:%02d)",
        _daily_hour(), _daily_minute(),
        _weekly_hour(), _weekly_minute(),
        _monthly_hour(), _monthly_minute(),
    )
    _scheduler_task = asyncio.create_task(_scheduler_loop())


async def stop_scheduler() -> None:
    """Cancel the background scheduler task. Called from FastAPI lifespan."""
    global _scheduler_task
    if _scheduler_task is not None:
        _scheduler_task.cancel()
        try:
            await _scheduler_task
        except asyncio.CancelledError:
            pass
        _scheduler_task = None
        log.info("Report scheduler stopped")


# ---------------------------------------------------------------------------
# Scheduler loop
# ---------------------------------------------------------------------------

_CHECK_INTERVAL_SEC = 60


async def _scheduler_loop() -> None:
    """Run forever, checking every 60 s whether scheduled reports should fire."""
    global _last_daily, _last_weekly, _last_monthly

    while True:
        try:
            now_kst = datetime.now(tz=KST)

            daily_date = _should_run_daily(now_kst)
            if daily_date:
                _last_daily = daily_date
                await asyncio.to_thread(_run_reports, "daily", daily_date)

            weekly_start = _should_run_weekly(now_kst)
            if weekly_start:
                _last_weekly = weekly_start
                await asyncio.to_thread(_run_reports, "weekly", weekly_start)

            monthly_start = _should_run_monthly(now_kst)
            if monthly_start:
                _last_monthly = monthly_start
                await asyncio.to_thread(_run_reports, "monthly", monthly_start)

        except asyncio.CancelledError:
            raise
        except Exception:
            log.exception("Scheduler loop error")

        await asyncio.sleep(_CHECK_INTERVAL_SEC)


# ---------------------------------------------------------------------------
# Schedule condition checks (all times compared in KST)
# ---------------------------------------------------------------------------


def _should_run_daily(now_kst: datetime) -> str | None:
    """Return yesterday's date string if daily schedule matches, else None."""
    if now_kst.hour == _daily_hour() and now_kst.minute == _daily_minute():
        yesterday = (now_kst - timedelta(days=1)).strftime("%Y-%m-%d")
        if yesterday != _last_daily:
            return yesterday
    return None


def _should_run_weekly(now_kst: datetime) -> str | None:
    """Return last Monday's date string if weekly schedule matches, else None."""
    if now_kst.weekday() == 0 and now_kst.hour == _weekly_hour() and now_kst.minute == _weekly_minute():
        last_monday = (now_kst - timedelta(days=7)).strftime("%Y-%m-%d")
        if last_monday != _last_weekly:
            return last_monday
    return None


def _should_run_monthly(now_kst: datetime) -> str | None:
    """Return previous month's first date string if monthly schedule matches, else None."""
    if now_kst.day == 1 and now_kst.hour == _monthly_hour() and now_kst.minute == _monthly_minute():
        prev_month_last = now_kst.replace(day=1) - timedelta(days=1)
        prev_month_first = prev_month_last.replace(day=1).strftime("%Y-%m-%d")
        if prev_month_first != _last_monthly:
            return prev_month_first
    return None


# ---------------------------------------------------------------------------
# Report generation runners
# ---------------------------------------------------------------------------


def _run_reports(period_type: str, period_start: str) -> None:
    """Generate reports for all active users for the given period."""
    log.info("Scheduled %s reports for %s", period_type, period_start)
    users = list_active_users()
    for user in users:
        _generate_all_projects(user, period_type, period_start)
    log.info("Scheduled %s reports complete for %s (%d users)", period_type, period_start, len(users))


# ---------------------------------------------------------------------------
# Per-user / per-project generation
# ---------------------------------------------------------------------------


def _generate_all_projects(user: dict, period_type: str, period_start: str) -> None:
    """Generate a report for every Langfuse project of the given user."""
    user_id = user["user_id"]
    projects = list_user_langfuse_projects(user_id)

    if projects:
        for proj in projects:
            _generate_for_user(user, proj, period_type, period_start)
    else:
        # Fall back to legacy per-user Langfuse settings
        legacy = get_user_langfuse_settings(user_id)
        if legacy:
            _generate_for_user(user, legacy, period_type, period_start)
        else:
            log.debug("No Langfuse settings for user %s — skipping", user_id)


def _resolve_project_id(project: dict) -> int | None:
    """Extract project_id from a project dict. Legacy settings have no id."""
    pid = project.get("id")
    return int(pid) if pid is not None else None


def _generate_for_user(
    user: dict,
    project: dict,
    period_type: str,
    period_start: str,
) -> None:
    """Generate and persist a single report for one user + project combination."""
    user_id = user["user_id"]
    project_id = _resolve_project_id(project)

    try:
        with langfuse_settings_scope(project):
            if period_type == "daily":
                client, _cfg, err = _build_langfuse_client({})
                if err:
                    log.warning(
                        "Langfuse client build failed for %s (project=%s): %s",
                        user_id, project_id, err,
                    )
                    return

                report_data = generate_daily_report(
                    client=client,
                    target_user_id=user_id,
                    date_str=period_start,
                    project_id=project_id,
                )

            elif period_type == "weekly":
                report_data = generate_weekly_report(
                    owner_id=user_id,
                    project_id=project_id,
                    target_user_id=user_id,
                    week_start=period_start,
                )

            elif period_type == "monthly":
                report_data = generate_monthly_report(
                    owner_id=user_id,
                    project_id=project_id,
                    target_user_id=user_id,
                    month_start=period_start,
                )
            else:
                return

            period_end = compute_period_end(period_start, period_type)

            insert_user_report(
                owner_id=user_id,
                project_id=project_id,
                target_user_id=user_id,
                period_type=period_type,
                period_start=period_start,
                period_end=period_end,
                summary=json.dumps(report_data.get("summary", {}), ensure_ascii=False),
                sessions=json.dumps(report_data.get("sessions", []), ensure_ascii=False),
                model_usage=json.dumps(report_data.get("model_usage", {}), ensure_ascii=False),
                timeline=json.dumps(report_data.get("timeline", []), ensure_ascii=False),
                trace_count=report_data.get("trace_count", 0),
                total_cost=report_data.get("total_cost", 0.0),
                llm_analysis=report_data.get("llm_analysis", ""),
            )
            log.info(
                "Scheduled %s report saved: user=%s project=%s period=%s",
                period_type, user_id, project_id, period_start,
            )

    except Exception as exc:
        log.warning(
            "Scheduled report failed for %s/%s (project=%s): %s",
            user_id, period_type, project_id, exc,
        )
