"""
Daily reminder scanner for subscription expiry and invoice overdue.
Runs at 09:00 Pacific/Auckland daily via APScheduler.
"""

from datetime import date, datetime, time, timedelta
from app.core.database import get_db
from app.core.models import (
    Subscription, SubscriptionStatus, Invoice, InvoiceStatus,
    Reminder, ReminderType, Customer, EmailLog, EmailLogStatus, TelegramLog, TelegramLogStatus
)
from app.services.email_service import send_email
from app.services.telegram_service import send_telegram_message, format_reminder_message
from app.core.config import get_telegram_config
import logging

logger = logging.getLogger(__name__)


class SafeFormatDict(dict):
    def __missing__(self, key):
        return "{" + key + "}"


def _empty_results():
    return {
        "subscription_expiry": {"checked": 0, "sent": 0, "skipped": 0},
        "invoice_overdue": {"checked": 0, "sent": 0, "skipped": 0},
        "custom": {"checked": 0, "sent": 0, "skipped": 0},
    }


async def check_and_send_reminders():
    """Main daily reminder check."""
    db = next(get_db())
    today = date.today()
    config = get_telegram_config()
    chat_id = config["chat_id"]
    
    results = _empty_results()
    sent_keys = set()
    
    active_reminders = (
        db.query(Reminder)
        .filter(Reminder.status == "active")
        .order_by(Reminder.reminder_type, Reminder.trigger_days.asc(), Reminder.id.asc())
        .all()
    )
    for reminder in active_reminders:
        await _process_reminder(db, reminder, today, chat_id, results, sent_keys)
    
    return results


async def _process_reminder(db, reminder, today, chat_id, results, sent_keys=None):
    reminder_type = _enum_value(reminder.reminder_type)
    if reminder_type == ReminderType.subscription_expiry.value:
        await _check_subscription_expiry(db, reminder, today, chat_id, results, sent_keys)
    elif reminder_type == ReminderType.invoice_overdue.value:
        await _check_invoice_overdue(db, reminder, today, chat_id, results, sent_keys)
    else:
        results["custom"]["skipped"] += 1


async def _check_subscription_expiry(db, reminder, today, chat_id, results, sent_keys=None):
    future_date = today + timedelta(days=reminder.trigger_days)
    
    subs = db.query(Subscription).filter(
        Subscription.status == SubscriptionStatus.active,
        Subscription.end_date != None,
        Subscription.end_date <= future_date,
        Subscription.end_date >= today,
    ).all()
    
    results["subscription_expiry"]["checked"] += len(subs)
    
    for sub in subs:
        customer = db.get(Customer, sub.customer_id)
        recipient = _customer_email_recipient(customer)
        days_left = (sub.end_date - today).days
        product_name = sub.product.name if sub.product else "Unknown"
        entity_name = f"{customer.name if customer else 'Unknown'} - {product_name}"
        details = f"Auto-renew: {'Yes' if sub.auto_renew else 'No'}"
        context = _subscription_context(sub, customer, product_name, days_left)
        tg_msg = _render_telegram_message(reminder, "subscription_expiry", entity_name, days_left, details, context)
        
        if reminder.send_telegram:
            sent = await _send_telegram_once(db, chat_id, tg_msg, today, sent_keys, "subscription", sub.id)
            if sent:
                results["subscription_expiry"]["sent"] += 1
        
        if reminder.send_email and recipient:
            subject, body = _render_email(reminder, "Subscription Expiry Reminder", f"Subscription {product_name} expires in {days_left} days.\nAuto-renew: {'Yes' if sub.auto_renew else 'No'}", context)
            sent = await _send_email_once(db, recipient, subject, body, today, sent_keys, "subscription", sub.id)
            if sent:
                results["subscription_expiry"]["sent"] += 1
        
        db.commit()


async def _check_invoice_overdue(db, reminder, today, chat_id, results, sent_keys=None):
    earliest_due_date = today - timedelta(days=reminder.trigger_days)
    overdue_invoices = db.query(Invoice).filter(
        Invoice.status.in_([InvoiceStatus.issued, InvoiceStatus.sent, InvoiceStatus.overdue]),
        Invoice.due_date != None,
        Invoice.due_date < today,
        Invoice.due_date <= earliest_due_date,
    ).all()
    
    results["invoice_overdue"]["checked"] += len(overdue_invoices)
    
    for invoice in overdue_invoices:
        if invoice.status not in [InvoiceStatus.overdue, InvoiceStatus.paid, InvoiceStatus.void]:
            invoice.status = InvoiceStatus.overdue
            db.commit()
        
        customer = db.get(Customer, invoice.customer_id)
        recipient = _customer_email_recipient(customer)
        days_overdue = (today - invoice.due_date).days
        details = f"Amount: {invoice.currency} {float(invoice.total_amount):,.2f}"
        context = _invoice_context(invoice, customer, days_overdue)
        tg_msg = _render_telegram_message(reminder, "invoice_overdue", invoice.invoice_number, days_overdue, details, context)
        
        if reminder.send_telegram:
            sent = await _send_telegram_once(db, chat_id, tg_msg, today, sent_keys, "invoice", invoice.id)
            if sent:
                results["invoice_overdue"]["sent"] += 1
        
        if reminder.send_email and recipient:
            subject, body = _render_email(reminder, "Invoice Overdue Reminder", f"Invoice {invoice.invoice_number} is {days_overdue} days overdue. Amount: {invoice.currency} {float(invoice.total_amount):,.2f}", context)
            sent = await _send_email_once(db, recipient, subject, body, today, sent_keys, "invoice", invoice.id)
            if sent:
                results["invoice_overdue"]["sent"] += 1
        
        db.commit()


def _enum_value(value):
    return value.value if hasattr(value, "value") else value


def _render_template(template, context):
    if not template:
        return ""
    return template.format_map(SafeFormatDict(context))


def _render_email(reminder, default_subject, default_body, context):
    template = reminder.email_template
    if not template:
        return default_subject, default_body
    subject = _render_template(template.subject or default_subject, context)
    body = _render_template(template.body or default_body, context)
    return subject, body


def _render_telegram_message(reminder, reminder_type, entity_name, days, details, context):
    if reminder.telegram_template:
        return _render_template(reminder.telegram_template, context)
    return format_reminder_message(reminder_type, entity_name, days, details)


def _customer_email_recipient(customer):
    if not customer:
        return None
    if getattr(customer, "email", None):
        return customer.email

    contacts = getattr(customer, "contacts", []) or []
    primary = next((contact for contact in contacts if getattr(contact, "is_primary", False)), None)
    if primary and getattr(primary, "email", None):
        return primary.email
    first_with_email = next((contact for contact in contacts if getattr(contact, "email", None)), None)
    return first_with_email.email if first_with_email else None


def _subscription_context(sub, customer, product_name, days_left):
    recipient = _customer_email_recipient(customer)
    return {
        "customer_name": customer.name if customer else "Unknown",
        "customer_email": recipient or "",
        "product_name": product_name,
        "days_left": days_left,
        "end_date": sub.end_date,
        "auto_renew": "Yes" if sub.auto_renew else "No",
    }


def _invoice_context(invoice, customer, days_overdue):
    recipient = _customer_email_recipient(customer)
    return {
        "customer_name": customer.name if customer else "Unknown",
        "customer_email": recipient or "",
        "invoice_number": invoice.invoice_number,
        "amount": f"{float(invoice.total_amount):,.2f}",
        "currency": invoice.currency,
        "days_overdue": days_overdue,
        "due_date": invoice.due_date,
    }


def _day_bounds(today):
    return datetime.combine(today, time.min), datetime.combine(today + timedelta(days=1), time.min)


def _already_sent_email_today(db, recipient, subject, body, today):
    start, end = _day_bounds(today)
    return db.query(EmailLog).filter(
        EmailLog.recipient == recipient,
        EmailLog.subject == subject,
        EmailLog.body == body,
        EmailLog.status == EmailLogStatus.sent,
        EmailLog.sent_at >= start,
        EmailLog.sent_at < end,
    ).first() is not None


def _already_sent_telegram_today(db, chat_id, message, today):
    start, end = _day_bounds(today)
    return db.query(TelegramLog).filter(
        TelegramLog.chat_id == chat_id,
        TelegramLog.message == message,
        TelegramLog.status == TelegramLogStatus.sent,
        TelegramLog.sent_at >= start,
        TelegramLog.sent_at < end,
    ).first() is not None


async def _send_email_once(db, recipient, subject, body, today, sent_keys, entity_type, entity_id):
    sent_key = ("email", entity_type, entity_id, recipient)
    if sent_keys is not None and sent_key in sent_keys:
        return False
    if _already_sent_email_today(db, recipient, subject, body, today):
        return False

    email_result = await send_email(recipient, subject, body)
    db.add(EmailLog(
        recipient=recipient,
        subject=subject,
        body=body,
        status=EmailLogStatus.sent if email_result["success"] else EmailLogStatus.failed,
        error_message=email_result.get("error"),
    ))
    if sent_keys is not None and email_result["success"]:
        sent_keys.add(sent_key)
    return bool(email_result["success"])


async def _send_telegram_once(db, chat_id, message, today, sent_keys, entity_type, entity_id):
    sent_key = ("telegram", entity_type, entity_id, chat_id)
    if sent_keys is not None and sent_key in sent_keys:
        return False
    if _already_sent_telegram_today(db, chat_id, message, today):
        return False

    result = await send_telegram_message(chat_id, message)
    db.add(TelegramLog(
        chat_id=chat_id,
        message=message,
        status=TelegramLogStatus.sent if result["success"] else TelegramLogStatus.failed,
        error_message=result.get("error"),
    ))
    if sent_keys is not None and result["success"]:
        sent_keys.add(sent_key)
    return bool(result["success"])


def setup_reminder_scheduler():
    """Set up APScheduler for daily reminders at 09:00 Pacific/Auckland."""
    from apscheduler.schedulers.asyncio import AsyncIOScheduler
    from apscheduler.triggers.cron import CronTrigger
    
    scheduler = AsyncIOScheduler()
    scheduler.add_job(
        check_and_send_reminders,
        CronTrigger(hour=9, minute=0, timezone="Pacific/Auckland"),
        id="daily_reminder_check",
        name="Daily Reminder Check",
        replace_existing=True,
    )
    scheduler.start()
    return scheduler
