"""Low-toner email/webhook alerting.

The report shows live levels; this fires an alert when a toner crosses a
threshold on its way DOWN. A warning email fires at or below 5 percent, a
critical email at 0 percent (empty). State lives in printersupplyalerts so an
alert fires once per crossing and re-arms only after a refill.

Recipients mirror the printedparts pattern: a plugin-scoped set of shopdb
users + roles + a free-text email list, falling back to the site-wide
alert_recipients; the webhook routes to a chosen support team's webhook,
falling back to the site-wide alert_webhook_url.
"""

import logging

from shopdb.api import db, Setting

from ..models import Printer, PrinterSupplyAlert
from .supply_parts import derivesupplytype, derivecolor, alerttier, TIER_RANK

logger = logging.getLogger(__name__)


def alert_recipients():
    """Merge selected shopdb users' account emails with the free-text list.

    Empty result means fall back to the site-wide alert_recipients."""
    from shopdb.api import User, Role
    recipients = []
    userids = (Setting.get('printers_alert_userids') or '').strip()
    for rawid in userids.split(','):
        rawid = rawid.strip()
        if not rawid.isdigit():
            continue
        user = db.session.get(User, int(rawid))
        if user and user.isactive and user.email:
            recipients.append(user.email)
    roleids = (Setting.get('printers_alert_roleids') or '').strip()
    for rawid in roleids.split(','):
        rawid = rawid.strip()
        if not rawid.isdigit():
            continue
        role = db.session.get(Role, int(rawid))
        if role:
            for user in role.users:
                if user.isactive and user.email:
                    recipients.append(user.email)
    freetext = Setting.get('printers_alert_email') or ''
    for addr in freetext.replace(';', ',').split(','):
        addr = addr.strip()
        if addr:
            recipients.append(addr)
    # de-dupe, preserve order
    seen = set()
    unique = []
    for addr in recipients:
        low = addr.lower()
        if low not in seen:
            seen.add(low)
            unique.append(addr)
    return unique


def _threshold(key, default):
    """Read a numeric threshold setting, tolerating blank/garbage values."""
    try:
        raw = Setting.get(key)
        return float(raw) if raw not in (None, '') else float(default)
    except (ValueError, TypeError):
        return float(default)


def site_alert_recipients():
    """Site-wide alert_recipients setting as a clean list (email fallback)."""
    raw = Setting.get('alert_recipients') or ''
    return [r.strip() for r in raw.replace(';', ',').split(',') if r.strip()]


def alert_team_webhook():
    """Webhook URL of the support team chosen for toner alerts, or None.

    printers_alert_supportteamid selects a SupportTeam; alerts route to that
    team's webhook. None -> send_webhook uses the site-wide default."""
    team_id = Setting.get('printers_alert_supportteamid')
    if not team_id:
        return None
    try:
        from shopdb.api import SupportTeam
        team = db.session.get(SupportTeam, int(team_id))
    except (ValueError, TypeError):
        return None
    return (team.webhookurl or None) if team else None


def _printer_ipaddress(printer):
    """Primary (else any) communication IP for a printer's asset, or None."""
    from shopdb.api import Communication
    asset = printer.asset
    if not asset:
        return None
    comm = Communication.query.filter_by(
        assetid=asset.assetid, isprimary=True).first() \
        or Communication.query.filter_by(assetid=asset.assetid).first()
    return comm.ipaddress if comm else None


def _fire_alert(printer, color, remaining, tier):
    """Best-effort email + webhook for one toner crossing. Never raises."""
    from shopdb.api import send_email, send_webhook
    name = printer.asset.name or printer.asset.assetnumber if printer.asset \
        else f'Printer {printer.printerid}'
    label = 'CRITICAL - empty' if tier == 'critical' else 'Low'
    subject = f'{label} toner: {name} ({color}) - {remaining:.0f}% remaining'
    base = (Setting.get('site_base_url') or '').rstrip('/')
    link = f'{base}/printers/{printer.printerid}' if base else ''
    link_html = f'<p><a href="{link}">View {name}</a></p>' if link else ''
    html = (f'<p><strong>{name}</strong> {color} toner is at '
            f'<strong>{remaining:.0f}%</strong> ({tier}).</p>{link_html}')
    webhook_text = f'{name} {color} toner at {remaining:.0f}% ({tier})'
    try:
        send_webhook(subject, webhook_text, url=alert_team_webhook())
        recipients = alert_recipients() or site_alert_recipients()
        if recipients:
            send_email(recipients, subject, html)
    except Exception:
        logger.exception('Toner alert failed for printer %s', printer.printerid)


def check_supplies(service=None):
    """Poll Zabbix for all active printers' toner and alert on crossings.

    Returns a summary dict. Intended to run on a schedule (a scheduled task or
    cron calling `flask printers check-toner-alerts`)."""
    from shopdb.api import Asset
    from .zabbix_service import ZabbixService

    service = service or ZabbixService()
    summary = {'printers': 0, 'polled': 0, 'alerts': 0, 'rearmed': 0}
    if not service.isconfigured:
        logger.warning('Zabbix not configured; skipping toner alert poll')
        return summary

    warning_threshold = _threshold('printers_alert_warning_threshold', 5)
    critical_threshold = _threshold('printers_alert_critical_threshold', 0)

    printers = db.session.query(Printer).join(Asset).filter(
        Asset.isactive == True).all()  # noqa: E712
    for printer in printers:
        summary['printers'] += 1
        ipaddress = _printer_ipaddress(printer)
        if not ipaddress or ipaddress == 'USB':
            continue
        supplies = service.getsuppliesbyip(ipaddress)
        if not supplies:
            continue
        summary['polled'] += 1
        for supply in supplies:
            supplyname = supply.get('name', '')
            if derivesupplytype(supplyname) != 'toner':
                continue
            try:
                remaining = float(supply.get('level', 0))
            except (TypeError, ValueError):
                continue
            color = supply.get('color') or derivecolor(supplyname)
            supplykey = color if color and color != 'none' else supplyname
            supplykey = supplykey[:64]
            tier = alerttier(remaining, warning_threshold, critical_threshold)

            row = PrinterSupplyAlert.query.filter_by(
                printerid=printer.printerid, supplykey=supplykey).first()
            prior = row.lasttier if row else 'ok'
            if tier == prior:
                continue
            if row is None:
                row = PrinterSupplyAlert(
                    printerid=printer.printerid, supplykey=supplykey,
                    lasttier=tier)
                db.session.add(row)
            else:
                row.lasttier = tier

            if TIER_RANK[tier] > TIER_RANK[prior]:
                _fire_alert(printer, color, remaining, tier)
                summary['alerts'] += 1
            else:
                summary['rearmed'] += 1

    db.session.commit()
    return summary
