import json

import pymysql
from flask import Blueprint, Response, jsonify, request

from blueprints.alerts import (
    battery_state,
    ensure_alert_state_table,
    evaluate_and_notify,
    send_sms_alert_detailed,
)
from config import MYSQL_DATABASE, MYSQL_HOST, MYSQL_PASSWORD, MYSQL_PORT, MYSQL_USER

bp = Blueprint("nutrientlevel", __name__)


def _get_conn():
    return pymysql.connect(
        host=MYSQL_HOST,
        port=MYSQL_PORT,
        user=MYSQL_USER,
        password=MYSQL_PASSWORD,
        database=MYSQL_DATABASE,
        cursorclass=pymysql.cursors.DictCursor,
    )


def _ensure_tables(conn):
    with conn.cursor() as cur:
        cur.execute(
            """
            CREATE TABLE IF NOT EXISTS NutrientLevelStatus (
                id INT AUTO_INCREMENT PRIMARY KEY,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                unit_id VARCHAR(16) NOT NULL,
                node_name VARCHAR(50) DEFAULT NULL,
                nutrient_low TINYINT(1) NOT NULL DEFAULT 0,
                nutrient_state VARCHAR(12) DEFAULT NULL,
                temp VARCHAR(10) DEFAULT NULL,
                humid VARCHAR(10) DEFAULT NULL,
                bat VARCHAR(10) DEFAULT NULL,
                command VARCHAR(20) DEFAULT NULL,
                mac VARCHAR(24) DEFAULT NULL,
                ip VARCHAR(45) DEFAULT NULL,
                ssid VARCHAR(64) DEFAULT NULL,
                firmware_version VARCHAR(24) DEFAULT NULL
            )
            """
        )
        try:
            cur.execute("ALTER TABLE NutrientLevelStatus ADD COLUMN ssid VARCHAR(64) DEFAULT NULL")
        except pymysql.OperationalError:
            pass
        cur.execute(
            """
            CREATE TABLE IF NOT EXISTS device_command (
                device_id VARCHAR(16) PRIMARY KEY,
                command VARCHAR(20) NOT NULL DEFAULT 'sleep',
                new_device_id VARCHAR(16) DEFAULT NULL,
                new_node_name VARCHAR(50) DEFAULT NULL
            )
            """
        )
    conn.commit()
    ensure_alert_state_table(conn)


def _parse_bool(v):
    if isinstance(v, bool):
        return v
    if isinstance(v, (int, float)):
        return v != 0
    if isinstance(v, str):
        s = v.strip().lower()
        if s in ("1", "true", "yes", "on", "low"):
            return True
        if s in ("0", "false", "no", "off", "normal"):
            return False
    return None


@bp.route("/reading", methods=["POST"])
def reading():
    try:
        data = request.get_json(force=True, silent=True)
        if not data:
            return jsonify({"ok": False, "error": "Invalid or missing JSON"}), 400

        unit_id = data.get("unit_id")
        node_name = data.get("node_name")
        temp = data.get("temp")
        humid = data.get("humid")
        mac = data.get("mac")
        ip = data.get("ip")
        ssid = data.get("ssid")
        bat = data.get("bat")
        firmware_version = data.get("firmware_version")
        nutrient_low = data.get("nutrient_low")
        nutrient_state = data.get("nutrient_state")

        if unit_id is None or temp is None or humid is None:
            return jsonify({"ok": False, "error": "Missing unit_id, temp, or humid"}), 400
        if not isinstance(unit_id, str) or not unit_id.strip():
            return jsonify({"ok": False, "error": "unit_id must be a non-empty string"}), 400

        unit_id = unit_id.strip()[:16]
        node_name = (node_name if isinstance(node_name, str) else "").strip()[:50] or None
        temp = str(temp).strip()[:10]
        humid = str(humid).strip()[:10]
        mac = (mac if isinstance(mac, str) else "").strip()[:24] or None
        ip = (ip if isinstance(ip, str) else "").strip()[:45] or None
        ssid = (ssid if isinstance(ssid, str) else "").strip()[:64] or None
        bat = (str(bat).strip()[:10] if bat is not None else None)
        firmware_version = (firmware_version if isinstance(firmware_version, str) else "").strip()[:24] or None

        low_bool = _parse_bool(nutrient_low)
        if low_bool is None and isinstance(nutrient_state, str):
            low_bool = _parse_bool(nutrient_state)
        if low_bool is None:
            low_bool = False
        nutrient_state = "low" if low_bool else "normal"

        conn = _get_conn()
        try:
            _ensure_tables(conn)
            with conn.cursor() as cur:
                cur.execute(
                    "SELECT command, new_device_id, new_node_name FROM device_command WHERE device_id = %s",
                    (unit_id,),
                )
                row = cur.fetchone()
                current_command = (row["command"] if row else "nosleep").strip().lower()
                if current_command not in ("sleep", "nosleep", "reboot"):
                    current_command = "nosleep"
                new_device_id = (row.get("new_device_id") or "").strip()[:16] if row else None
                new_node_name = (row.get("new_node_name") or "").strip()[:50] if row else None

                cur.execute(
                    """
                    INSERT INTO NutrientLevelStatus
                        (unit_id, node_name, nutrient_low, nutrient_state, temp, humid, bat, command, mac, ip, ssid, firmware_version)
                    VALUES
                        (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                    """,
                    (
                        unit_id,
                        node_name,
                        1 if low_bool else 0,
                        nutrient_state,
                        temp,
                        humid,
                        bat,
                        current_command,
                        mac,
                        ip,
                        ssid,
                        firmware_version,
                    ),
                )

                if new_device_id:
                    cur.execute("UPDATE device_command SET new_device_id = NULL WHERE device_id = %s", (unit_id,))
                if new_node_name:
                    cur.execute("UPDATE device_command SET new_node_name = NULL WHERE device_id = %s", (unit_id,))
                if current_command == "reboot":
                    cur.execute("UPDATE device_command SET command = 'nosleep' WHERE device_id = %s", (unit_id,))
                evaluate_and_notify(
                    conn=conn,
                    source_type="nutrient",
                    source_id=unit_id,
                    metric="nutrient",
                    current_state=nutrient_state,
                    alert_message=(
                        f"SolarMon alert: nutrient level low on {node_name or unit_id}."
                    ),
                )
                batt_state = battery_state(bat)
                if batt_state:
                    evaluate_and_notify(
                        conn=conn,
                        source_type="nutrient",
                        source_id=unit_id,
                        metric="battery",
                        current_state=batt_state,
                        alert_message=(
                            f"SolarMon alert: battery low on {node_name or unit_id}. "
                            f"Battery={bat}%"
                        ),
                    )
            conn.commit()
        finally:
            conn.close()

        body = json.dumps(
            {
                "ok": True,
                "command": current_command,
                "device_id": new_device_id if new_device_id else unit_id,
                "node_name": new_node_name if new_node_name else (node_name or "NEW"),
            },
            separators=(",", ":"),
        )
        return Response(body, status=200, mimetype="application/json")
    except pymysql.Error:
        return jsonify({"ok": False, "error": "Database error"}), 500
    except Exception as e:
        return jsonify({"ok": False, "error": str(e)}), 500


@bp.route("/api/command", methods=["POST"])
def api_set_command():
    data = request.get_json(force=True, silent=True) or {}
    unit_id = (data.get("unit_id") or "").strip()[:16]
    cmd = (data.get("command") or "").strip().lower()
    if not unit_id:
        return jsonify({"ok": False, "error": "unit_id required"}), 400
    if cmd not in ("sleep", "nosleep", "reboot"):
        return jsonify({"ok": False, "error": "command must be sleep, nosleep, or reboot"}), 400

    try:
        conn = _get_conn()
        try:
            _ensure_tables(conn)
            with conn.cursor() as cur:
                cur.execute(
                    "INSERT INTO device_command (device_id, command) VALUES (%s, %s) "
                    "ON DUPLICATE KEY UPDATE command = VALUES(command)",
                    (unit_id, cmd),
                )
            conn.commit()
        finally:
            conn.close()
        return jsonify({"ok": True, "unit_id": unit_id, "command": cmd})
    except pymysql.Error as e:
        return jsonify({"ok": False, "error": str(e)}), 500


@bp.route("/api/set_node_id_name", methods=["POST"])
def api_set_node_id_name():
    data = request.get_json(force=True, silent=True) or {}
    unit_id = (data.get("unit_id") or "").strip()[:16]
    new_unit_id = (data.get("new_unit_id") or "").strip()[:16]
    new_node_name = (data.get("new_node_name") or "").strip()[:50]
    if not unit_id or not new_unit_id or not new_node_name:
        return jsonify({"ok": False, "error": "unit_id, new_unit_id, and new_node_name are required"}), 400

    try:
        conn = _get_conn()
        try:
            _ensure_tables(conn)
            with conn.cursor() as cur:
                cur.execute(
                    "UPDATE NutrientLevelStatus SET unit_id = %s, node_name = %s WHERE unit_id = %s",
                    (new_unit_id, new_node_name, unit_id),
                )
                cur.execute(
                    "INSERT INTO device_command (device_id, command, new_device_id, new_node_name) VALUES (%s, 'nosleep', %s, %s) "
                    "ON DUPLICATE KEY UPDATE new_device_id = VALUES(new_device_id), new_node_name = VALUES(new_node_name)",
                    (unit_id, new_unit_id, new_node_name),
                )
            conn.commit()
        finally:
            conn.close()
        return jsonify({"ok": True})
    except pymysql.Error as e:
        return jsonify({"ok": False, "error": str(e)}), 500


@bp.route("/api/test_sms", methods=["POST"])
def api_test_sms():
    data = request.get_json(force=True, silent=True) or {}
    unit_id = (data.get("unit_id") or "").strip()[:16]
    node_name = (data.get("node_name") or "").strip()[:50]
    if not unit_id:
        return jsonify({"ok": False, "error": "unit_id required"}), 400

    display_name = node_name or unit_id
    sent, err = send_sms_alert_detailed(
        f"SolarMon test SMS from Nutrient Level node: {display_name} ({unit_id})."
    )
    if not sent:
        return jsonify({"ok": False, "error": f"SMS send failed: {err or 'unknown error'}"}), 500
    return jsonify({"ok": True, "unit_id": unit_id})
