import logging
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime

from config import (
    ALERT_TO_NUMBER,
    BATTERY_LOW_PERCENT,
    MOISTURE_DRY_THRESHOLD,
    PUSHOVER_APP_TOKEN,
    PUSHOVER_USER_KEY,
    SMS_PROVIDER,
    TWILIO_ACCOUNT_SID,
    TWILIO_AUTH_TOKEN,
    TWILIO_FROM_NUMBER,
)

logger = logging.getLogger(__name__)

def ensure_alert_state_table(conn):
    with conn.cursor() as cur:
        cur.execute(
            """
            CREATE TABLE IF NOT EXISTS alert_state (
                source_type VARCHAR(24) NOT NULL,
                source_id VARCHAR(32) NOT NULL,
                metric VARCHAR(24) NOT NULL,
                last_state VARCHAR(16) NOT NULL,
                last_notified_at TIMESTAMP NULL DEFAULT NULL,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (source_type, source_id, metric)
            )
            """
        )


def _is_twilio_configured():
    return all(
        [
            (TWILIO_ACCOUNT_SID or "").strip(),
            (TWILIO_AUTH_TOKEN or "").strip(),
            (TWILIO_FROM_NUMBER or "").strip(),
            (ALERT_TO_NUMBER or "").strip(),
        ]
    )


def _send_via_twilio(message):
    if not _is_twilio_configured():
        msg = "Twilio config missing (SID/token/from/to)."
        logger.warning("SMS alert skipped: %s", msg)
        return False, msg
    try:
        from twilio.rest import Client

        client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
        client.messages.create(
            body=message,
            from_=(TWILIO_FROM_NUMBER or "").strip(),
            to=(ALERT_TO_NUMBER or "").strip(),
        )
        return True, None
    except Exception as exc:
        logger.exception("Failed to send Twilio SMS alert: %s", exc)
        return False, str(exc)


def _send_via_log(message):
    logger.warning("SMS provider=log message=%s", message)
    return True, None


def _send_via_noop(_message):
    return True, None


def _send_via_aws_sns(message):
    try:
        import boto3
    except Exception:
        return False, "boto3 is not installed for aws_sns provider."
    if not (ALERT_TO_NUMBER or "").strip():
        return False, "ALERT_TO_NUMBER missing."
    try:
        client = boto3.client("sns")
        client.publish(PhoneNumber=(ALERT_TO_NUMBER or "").strip(), Message=message)
        return True, None
    except Exception as exc:
        logger.exception("Failed to send aws_sns SMS alert: %s", exc)
        return False, str(exc)


def _is_pushover_configured():
    return all([(PUSHOVER_APP_TOKEN or "").strip(), (PUSHOVER_USER_KEY or "").strip()])


def _send_via_pushover(message):
    if not _is_pushover_configured():
        return False, "Pushover config missing (PUSHOVER_APP_TOKEN/PUSHOVER_USER_KEY)."
    payload = urllib.parse.urlencode(
        {
            "token": (PUSHOVER_APP_TOKEN or "").strip(),
            "user": (PUSHOVER_USER_KEY or "").strip(),
            "message": message,
            "title": "SolarMon Alert",
        }
    ).encode("utf-8")
    req = urllib.request.Request(
        "https://api.pushover.net/1/messages.json",
        data=payload,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            body = resp.read().decode("utf-8", errors="replace")
            if resp.status < 200 or resp.status >= 300:
                return False, f"Pushover HTTP {resp.status}: {body}"
        return True, None
    except urllib.error.HTTPError as exc:
        try:
            err_body = exc.read().decode("utf-8", errors="replace")
        except Exception:
            err_body = ""
        detail = f"HTTP {exc.code}"
        if err_body:
            detail += f": {err_body}"
        logger.exception("Failed to send pushover alert: %s", detail)
        return False, f"Pushover {detail}"
    except Exception as exc:
        logger.exception("Failed to send pushover alert: %s", exc)
        return False, str(exc)


def _provider_name():
    return (SMS_PROVIDER or "pushover").strip().lower()


def send_sms_alert(message):
    ok, _err = send_sms_alert_detailed(message)
    return ok


def send_sms_alert_detailed(message):
    provider = _provider_name()
    if provider == "twilio":
        return _send_via_twilio(message)
    if provider == "pushover":
        return _send_via_pushover(message)
    if provider == "aws_sns":
        return _send_via_aws_sns(message)
    if provider == "log":
        return _send_via_log(message)
    if provider == "noop":
        return _send_via_noop(message)
    return False, f"Unsupported SMS_PROVIDER '{provider}'."


def parse_percent(value):
    if value is None:
        return None
    if isinstance(value, (int, float)):
        pct = float(value)
    elif isinstance(value, str):
        s = value.strip().replace("%", "")
        if not s:
            return None
        try:
            pct = float(s)
        except ValueError:
            return None
    else:
        return None
    if pct < 0 or pct > 100:
        return None
    return pct


def moisture_state(moisture_value):
    try:
        moisture_int = int(moisture_value)
    except (TypeError, ValueError):
        return None
    return "low" if moisture_int > MOISTURE_DRY_THRESHOLD else "normal"


def battery_state(percent_value):
    pct = parse_percent(percent_value)
    if pct is None:
        return None
    return "low" if pct <= BATTERY_LOW_PERCENT else "normal"


def nutrient_state(is_low):
    return "low" if bool(is_low) else "normal"


def pump_off_pressure_fault_state(pump_on, error_pressure_low):
    """Alert when pump is off but low-pressure error is active (unexpected fault)."""
    if not bool(pump_on) and bool(error_pressure_low):
        return "low"
    return "normal"


def evaluate_and_notify(conn, source_type, source_id, metric, current_state, alert_message):
    if not current_state:
        return {"notified": False, "state_changed": False, "last_state": None}

    with conn.cursor() as cur:
        cur.execute(
            "SELECT last_state FROM alert_state WHERE source_type = %s AND source_id = %s AND metric = %s",
            (source_type, source_id, metric),
        )
        row = cur.fetchone()
        previous_state = row["last_state"] if row else None
        state_changed = previous_state != current_state
        notified = False

        if state_changed and current_state == "low":
            notified = send_sms_alert(alert_message)

        cur.execute(
            """
            INSERT INTO alert_state (source_type, source_id, metric, last_state, last_notified_at)
            VALUES (%s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                last_state = VALUES(last_state),
                last_notified_at = VALUES(last_notified_at)
            """,
            (
                source_type,
                source_id,
                metric,
                current_state,
                datetime.utcnow() if notified else None,
            ),
        )
        return {"notified": notified, "state_changed": state_changed, "last_state": previous_state}
