import asyncio
import logging

import db
import mail

logger = logging.getLogger(__name__)

# Base polling interval — how often the loop wakes up to see which rules are due.
# Individual rules control their own check_interval (stored in alarm_rule.check_interval).
_BASE_INTERVAL = 60  # seconds

_OPERATORS = {
    "<": lambda v, t: v < t,
    ">": lambda v, t: v > t,
    "<=": lambda v, t: v <= t,
    ">=": lambda v, t: v >= t,
    "=": lambda v, t: v == t,
}


def _condition_met(operator: str, value: float, threshold: float) -> bool:
    fn = _OPERATORS.get(operator)
    return fn(value, threshold) if fn else False


async def _check_rules() -> None:
    # Only fetch rules whose check_interval has elapsed since last_checked_at
    rules = await db.fetch(
        """
        SELECT id, name, department_id, entity, metric, operator, threshold, check_interval
        FROM alarm_rule
        WHERE enabled = TRUE
          AND (
              last_checked_at IS NULL
              OR last_checked_at < NOW() - (check_interval || ' seconds')::INTERVAL
          )
        """
    )

    for rule in rules:
        rule_id = str(rule["id"])
        rule_name = rule["name"]
        department_id = str(rule["department_id"])
        entity = rule["entity"]
        metric = rule["metric"]
        operator = rule["operator"]
        threshold = rule["threshold"]

        # Stamp last_checked_at immediately so concurrent checker instances don't double-fire
        # last_value is updated below once we have the reading; set to NULL for now
        await db.execute(
            "UPDATE alarm_rule SET last_checked_at = NOW() WHERE id = $1::uuid",
            rule_id,
        )

        # Most recent sensor reading for this rule's target
        latest = await db.fetchrow(
            """
            SELECT value, time
            FROM sensor_data
            WHERE department_id = $1::uuid
              AND entity        = $2
              AND metric        = $3
            ORDER BY time DESC
            LIMIT 1
            """,
            department_id,
            entity,
            metric,
        )

        if latest is None:
            continue

        current_value = latest["value"]
        occurred_at = latest["time"]

        await db.execute(
            "UPDATE alarm_rule SET last_checked_value = $1 WHERE id = $2::uuid",
            current_value,
            rule_id,
        )

        # Last alarm event for this rule (determines current state)
        last_event = await db.fetchrow(
            """
            SELECT status
            FROM alarm_event
            WHERE rule_id = $1::uuid
            ORDER BY occurred_at DESC
            LIMIT 1
            """,
            rule_id,
        )

        last_status = last_event["status"] if last_event else None
        breach = _condition_met(operator, current_value, threshold)

        if breach and last_status != "triggered":
            await db.execute(
                "INSERT INTO alarm_event (rule_id, status, value) VALUES ($1::uuid, 'triggered', $2)",
                rule_id,
                current_value,
            )

            recipients = await db.fetch(
                """
                SELECT ua.email, ua.name
                FROM alarm_recipient ar
                JOIN user_account ua ON ua.id = ar.user_id
                WHERE ar.rule_id = $1::uuid
                """,
                rule_id,
            )

            subject = f"LEAF Alarm: {rule_name}"
            body = (
                f'Alarm "{rule_name}" has been triggered.\n\n'
                f"Entity    : {entity}\n"
                f"Metric    : {metric}\n"
                f"Condition : {metric} {operator} {threshold}\n"
                f"Value     : {current_value}\n"
                f"Time      : {occurred_at.strftime('%Y-%m-%d %H:%M:%S UTC')}\n"
            )

            for recipient in recipients:
                try:
                    await mail.send_email(
                        to=recipient["email"], subject=subject, body=body
                    )
                except Exception:
                    logger.exception(
                        "Failed to send alarm email to %s for rule %s",
                        recipient["email"],
                        rule_id,
                    )

            logger.info(
                "Alarm triggered: rule=%s entity=%s metric=%s value=%s",
                rule_id,
                entity,
                metric,
                current_value,
            )

        elif not breach and last_status == "triggered":
            await db.execute(
                "INSERT INTO alarm_event (rule_id, status, value) VALUES ($1::uuid, 'resolved', $2)",
                rule_id,
                current_value,
            )
            logger.info(
                "Alarm resolved: rule=%s entity=%s metric=%s value=%s",
                rule_id,
                entity,
                metric,
                current_value,
            )


async def alarm_checker_loop() -> None:
    logger.info("Alarm checker started (base interval=%ds)", _BASE_INTERVAL)
    while True:
        try:
            await _check_rules()
        except Exception:
            logger.exception("Alarm checker error")
        await asyncio.sleep(_BASE_INTERVAL)
