"""
daemon.py - Background polling loop for scheduled tasks

Override poll_callback() with your domain-specific logic.
"""

import os
import time
import logging
from datetime import datetime, timezone, timedelta

import core.postgres as postgres
import core.notifications as notifications
import core.adaptive_meds as adaptive_meds

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", 60))


def _user_now_for(user_uuid):
    """Get current datetime in a user's timezone using their stored offset."""
    prefs = postgres.select_one("user_preferences", {"user_uuid": user_uuid})
    offset_minutes = 0
    if prefs and prefs.get("timezone_offset") is not None:
        offset_minutes = prefs["timezone_offset"]
    # JS getTimezoneOffset: positive = behind UTC, so negate
    tz_obj = timezone(timedelta(minutes=-offset_minutes))
    return datetime.now(tz_obj)


def check_medication_reminders():
    """Check for medications due now and send notifications."""
    try:
        from datetime import date as date_type

        meds = postgres.select("medications", where={"active": True})

        # Group by user so we only look up timezone once per user
        user_meds = {}
        for med in meds:
            uid = med.get("user_uuid")
            if uid not in user_meds:
                user_meds[uid] = []
            user_meds[uid].append(med)

        for user_uuid, user_med_list in user_meds.items():
            now = _user_now_for(user_uuid)
            current_time = now.strftime("%H:%M")
            current_day = now.strftime("%a").lower()
            today = now.date()
            today_str = today.isoformat()

            for med in user_med_list:
                freq = med.get("frequency", "daily")

                # Skip as_needed -- no scheduled reminders for PRN
                if freq == "as_needed":
                    continue

                # Day-of-week check for specific_days
                if freq == "specific_days":
                    med_days = med.get("days_of_week", [])
                    if current_day not in med_days:
                        continue

                # Interval check for every_n_days
                if freq == "every_n_days":
                    start = med.get("start_date")
                    interval = med.get("interval_days")
                    if start and interval:
                        start_d = (
                            start
                            if isinstance(start, date_type)
                            else datetime.strptime(str(start), "%Y-%m-%d").date()
                        )
                        if (today - start_d).days < 0 or (
                            today - start_d
                        ).days % interval != 0:
                            continue
                    else:
                        continue

                # Time check
                times = med.get("times", [])
                if current_time not in times:
                    continue

                # Already taken today? Check by created_at date
                logs = postgres.select(
                    "med_logs", where={"medication_id": med["id"], "action": "taken"}
                )
                already_taken = any(
                    log.get("scheduled_time") == current_time
                    and str(log.get("created_at", ""))[:10] == today_str
                    for log in logs
                )
                if already_taken:
                    continue

                user_settings = notifications.getNotificationSettings(user_uuid)
                if user_settings:
                    msg = f"Time to take {med['name']} ({med['dosage']} {med['unit']}) · {current_time}"
                    notifications._sendToEnabledChannels(
                        user_settings, msg, user_uuid=user_uuid
                    )
    except Exception as e:
        logger.error(f"Error checking medication reminders: {e}")


def check_routine_reminders():
    """Check for scheduled routines due now and send notifications."""
    try:
        schedules = postgres.select("routine_schedules", where={"remind": True})

        for schedule in schedules:
            routine = postgres.select_one("routines", {"id": schedule["routine_id"]})
            if not routine:
                continue

            now = _user_now_for(routine["user_uuid"])
            current_time = now.strftime("%H:%M")
            current_day = now.strftime("%a").lower()

            if current_time != schedule.get("time"):
                continue
            days = schedule.get("days", [])
            if current_day not in days:
                continue

            user_settings = notifications.getNotificationSettings(routine["user_uuid"])
            if user_settings:
                msg = f"Time to start your routine: {routine['name']}"
                notifications._sendToEnabledChannels(
                    user_settings, msg, user_uuid=routine["user_uuid"]
                )
    except Exception as e:
        logger.error(f"Error checking routine reminders: {e}")


def check_refills():
    """Check for medications running low on refills."""
    try:
        meds = postgres.select("medications")
        for med in meds:
            qty = med.get("quantity_remaining")
            if qty is not None and qty <= 7:
                user_settings = notifications.getNotificationSettings(med["user_uuid"])
                if user_settings:
                    msg = f"Low on {med['name']}: only {qty} doses remaining. Time to refill!"
                    notifications._sendToEnabledChannels(
                        user_settings, msg, user_uuid=med["user_uuid"]
                    )
    except Exception as e:
        logger.error(f"Error checking refills: {e}")


def create_daily_adaptive_schedules():
    """Create today's medication schedules with adaptive timing."""
    try:
        from datetime import date as date_type

        meds = postgres.select("medications", where={"active": True})

        for med in meds:
            user_uuid = med.get("user_uuid")
            med_id = med.get("id")
            times = med.get("times", [])

            if not times:
                continue

            # Create daily schedule with adaptive adjustments
            adaptive_meds.create_daily_schedule(user_uuid, med_id, times)

    except Exception as e:
        logger.error(f"Error creating daily adaptive schedules: {e}")


def check_adaptive_medication_reminders():
    """Check for medications due now with adaptive timing."""
    try:
        from datetime import date as date_type

        meds = postgres.select("medications", where={"active": True})

        # Group by user
        user_meds = {}
        for med in meds:
            uid = med.get("user_uuid")
            if uid not in user_meds:
                user_meds[uid] = []
            user_meds[uid].append(med)

        for user_uuid, user_med_list in user_meds.items():
            now = _user_now_for(user_uuid)
            current_time = now.strftime("%H:%M")
            today = now.date()

            # Check if adaptive timing is enabled
            settings = adaptive_meds.get_adaptive_settings(user_uuid)
            adaptive_enabled = settings and settings.get("adaptive_timing_enabled")

            for med in user_med_list:
                freq = med.get("frequency", "daily")

                if freq == "as_needed":
                    continue

                # Day-of-week check
                if freq == "specific_days":
                    current_day = now.strftime("%a").lower()
                    med_days = med.get("days_of_week", [])
                    if current_day not in med_days:
                        continue

                # Interval check
                if freq == "every_n_days":
                    start = med.get("start_date")
                    interval = med.get("interval_days")
                    if start and interval:
                        start_d = (
                            start
                            if isinstance(start, date_type)
                            else datetime.strptime(str(start), "%Y-%m-%d").date()
                        )
                        if (today - start_d).days < 0 or (
                            today - start_d
                        ).days % interval != 0:
                            continue
                    else:
                        continue

                # Get today's schedule
                schedules = postgres.select(
                    "medication_schedules",
                    where={
                        "user_uuid": user_uuid,
                        "medication_id": med["id"],
                        "adjustment_date": today,
                        "status": "pending",
                    },
                )

                for sched in schedules:
                    # Check if it's time to take this med
                    if adaptive_enabled:
                        # Use adjusted time
                        check_time = sched.get("adjusted_time")
                    else:
                        # Use base time
                        check_time = sched.get("base_time")

                    if check_time != current_time:
                        continue

                    # Check if already taken
                    logs = postgres.select(
                        "med_logs",
                        where={
                            "medication_id": med["id"],
                            "user_uuid": user_uuid,
                            "action": "taken",
                        },
                    )

                    already_taken = any(
                        str(log.get("created_at", ""))[:10] == today.isoformat()
                        for log in logs
                    )

                    if already_taken:
                        continue

                    # Send notification
                    user_settings = notifications.getNotificationSettings(user_uuid)
                    if user_settings:
                        offset = sched.get("adjustment_minutes", 0)
                        if offset > 0:
                            msg = f"⏰ Time to take {med['name']} ({med['dosage']} {med['unit']}) · {check_time} (adjusted +{offset}min)"
                        else:
                            msg = f"⏰ Time to take {med['name']} ({med['dosage']} {med['unit']}) · {check_time}"

                        notifications._sendToEnabledChannels(
                            user_settings, msg, user_uuid=user_uuid
                        )

    except Exception as e:
        logger.error(f"Error checking adaptive medication reminders: {e}")


def check_nagging():
    """Check for missed medications and send nag notifications."""
    try:
        from datetime import date as date_type

        # Get all active medications
        meds = postgres.select("medications", where={"active": True})

        for med in meds:
            user_uuid = med.get("user_uuid")
            med_id = med.get("id")

            # Get user's settings
            settings = adaptive_meds.get_adaptive_settings(user_uuid)
            if not settings or not settings.get("nagging_enabled"):
                continue

            now = datetime.utcnow()
            today = now.date()

            # Get today's schedules
            schedules = postgres.select(
                "medication_schedules",
                where={
                    "user_uuid": user_uuid,
                    "medication_id": med_id,
                    "adjustment_date": today,
                    "status": "pending",
                },
            )

            for sched in schedules:
                # Check if we should nag
                should_nag, reason = adaptive_meds.should_send_nag(
                    user_uuid, med_id, sched.get("adjusted_time"), now
                )

                if not should_nag:
                    continue

                # Get the time to display
                adaptive_enabled = settings.get("adaptive_timing_enabled")
                if adaptive_enabled:
                    display_time = sched.get("adjusted_time")
                else:
                    display_time = sched.get("base_time")

                # Send nag notification
                user_settings = notifications.getNotificationSettings(user_uuid)
                if user_settings:
                    nag_count = sched.get("nag_count", 0) + 1
                    max_nags = settings.get("max_nag_count", 4)

                    msg = f"🔔 {med['name']} reminder {nag_count}/{max_nags}: You missed your {display_time} dose. Please take it now!"

                    notifications._sendToEnabledChannels(
                        user_settings, msg, user_uuid=user_uuid
                    )

                    # Record that we sent a nag
                    adaptive_meds.record_nag_sent(
                        user_uuid, med_id, sched.get("adjusted_time")
                    )

                    logger.info(
                        f"Sent nag {nag_count}/{max_nags} for {med['name']} to user {user_uuid}"
                    )

    except Exception as e:
        logger.error(f"Error checking nags: {e}")


def poll_callback():
    """Called every POLL_INTERVAL seconds."""
    # Create daily schedules at midnight
    now = datetime.utcnow()
    if now.hour == 0 and now.minute < POLL_INTERVAL / 60:
        create_daily_adaptive_schedules()

    # Check reminders with adaptive timing
    check_adaptive_medication_reminders()

    # Check for nags
    check_nagging()

    # Original checks
    check_routine_reminders()
    check_refills()


def daemon_loop():
    logger.info("Scheduler daemon starting")
    while True:
        try:
            poll_callback()
        except Exception as e:
            logger.error(f"Poll callback error: {e}")
        time.sleep(POLL_INTERVAL)


if __name__ == "__main__":
    daemon_loop()
