﻿import platform
import socket
import uuid
import requests
from datetime import datetime
import sqlite3
import logging

import json
import datetime
from pypos.core.utils.config_utils import read_config, read_endpoint_config, get_current_server_hash
from pypos.core.utils.db_helper import connect_sqlite
from pypos.core.utils.http_retry import post_with_retry, get_with_retry

LOGGER = logging.getLogger(__name__)
_NUMERIC_PARSE_EXCEPTIONS = (TypeError, ValueError)
_DB_EXCEPTIONS = (sqlite3.Error, OSError, RuntimeError, TypeError, ValueError)
_JSON_PARSE_EXCEPTIONS = (ValueError, json.JSONDecodeError)

def _build_device_endpoint(path_key):
    cfg = read_config() or {}
    base_url = str(cfg.get("api_base_url") or "").rstrip("/")
    path = str(cfg.get(path_key) or "").strip()
    if path and not path.startswith("/"):
        path = "/" + path
    if not base_url:
        return "", cfg, "missing_api_base_url"
    if not path:
        return "", cfg, f"missing_{path_key}"
    return f"{base_url}{path}", cfg, ""


def _resolve_toko_id(payload=None, cfg=None):
    if isinstance(payload, dict):
        raw = payload.get("toko_id")
        try:
            value = int(raw)
            if value > 0:
                return value
        except _NUMERIC_PARSE_EXCEPTIONS:
            pass
    try:
        device_info = get_active_device_info(get_device_id()) or {}
        raw = device_info.get("toko_id")
        value = int(raw)
        if value > 0:
            return value
    except _DB_EXCEPTIONS:
        pass
    try:
        raw = (cfg or {}).get("toko_id")
        value = int(raw)
        if value > 0:
            return value
    except _NUMERIC_PARSE_EXCEPTIONS:
        pass
    return None

def post_device_registration(data):
    url, cfg, endpoint_error = _build_device_endpoint("ep_device")
    if endpoint_error:
        return {"status": 0, "reason": endpoint_error}

    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }

    try:
        payload = dict(data) if isinstance(data, dict) else {}
        payload["dtime_in"] = payload.get("dtime_in") or datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        toko_id = _resolve_toko_id(payload=payload, cfg=cfg)
        if not toko_id:
            return {"status": 0, "reason": "missing_toko_id"}
        payload["toko_id"] = toko_id
        payload["status"] = int(payload.get("status") or 1)

        LOGGER.debug("[DEBUG DEVICE REG] POST %s", url)
        LOGGER.debug("[DEBUG DEVICE REG] Payload keys: %s", list(payload.keys()))

        response = post_with_retry(
            url,
            data=json.dumps(payload),
            headers=headers,
            timeout=int(cfg.get("request_timeout", 10))
        )

        LOGGER.debug("[DEBUG DEVICE REG] Status: %s", response.status_code)
        LOGGER.debug("[DEBUG DEVICE REG] Content-Type: %s", response.headers.get("Content-Type", ""))
        LOGGER.debug("[DEBUG DEVICE REG] Body: %s", (response.text or "")[:500])

        try:
            return response.json()
        except _JSON_PARSE_EXCEPTIONS as exc:
            LOGGER.warning("Response registrasi bukan JSON: %s", exc)
            return {"status": 0, "reason": "invalid_json"}

    except requests.exceptions.RequestException as exc:
        LOGGER.warning("Error submit registrasi endpoint device: %s", exc)
        return {
            "status": 0,
            "reason": "endpoint_device_registration_error",
            "message": f"Endpoint registrasi device bermasalah: {str(exc)}",
        }


def cek_device_ke_server(device_id):
    url, cfg, endpoint_error = _build_device_endpoint("ep_device_cek")
    if endpoint_error:
        return {"status": 0, "reason": endpoint_error}

    params = {"machine_id": str(device_id or "")}

    try:
        response = get_with_retry(
            url,
            params=params,
            timeout=int(cfg.get("request_timeout", 10))
        )

        try:
            return response.json()
        except _JSON_PARSE_EXCEPTIONS as exc:
            LOGGER.warning("Response cek device bukan JSON: %s", exc)
            return {"status": 500, "reason": "invalid_json"}

    except requests.exceptions.RequestException as exc:
        LOGGER.warning("Error checking device registration endpoint: %s", exc)
        return {
            "status": 500,
            "reason": "endpoint_device_check_error",
            "message": f"Endpoint cek device bermasalah: {str(exc)}",
        }

def is_device_not_registered(result):
    if not isinstance(result, dict):
        return False
    status = result.get("status")
    if str(status) in ("404", "0"):
        return True
    reason = (result.get("reason") or result.get("message") or "").lower()
    if "not registered" in reason or "belum terdaftar" in reason:
        return True
    return False

def simpan_device_lokal(data_reg, device_id=None):
    try:
        from pypos.core.utils.path_utils import get_db_path
        db_path = get_db_path()
        conn = connect_sqlite(db_path)
        conn.execute("PRAGMA foreign_keys = ON")
        _ensure_device_server_hash_column(conn)
        cursor = conn.cursor()

        machine_id = data_reg.get("machine_id") if isinstance(data_reg, dict) else None
        if not machine_id and device_id:
            machine_id = str(device_id)
        if not machine_id:
            conn.close()
            return False

        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        server_hash = get_current_server_hash()
        payload = {
            "last_update": data_reg.get("last_update", now),
            "machine_id": machine_id,
            "server_hash": data_reg.get("server_hash", server_hash),
            "com_info": data_reg.get("com_info", get_com_info()),
            "cpu_info": data_reg.get("cpu_info", get_cpu_info()),
            "browser_verif": data_reg.get("browser_verif", get_browser_verif()),
            "nama": data_reg.get("nama", data_reg.get("alias", "")),
            "alias": data_reg.get("alias", ""),
            "keterangan": data_reg.get("keterangan", ""),
            "cabang_id": data_reg.get("cabang_id", 0),
            "cabang_nama": data_reg.get("cabang_nama", ""),
            "kelurahan": data_reg.get("kelurahan", ""),
            "kecamatan": data_reg.get("kecamatan", ""),
            "kabupaten": data_reg.get("kabupaten", ""),
            "propinsi": data_reg.get("propinsi", ""),
            "alamat": data_reg.get("alamat", ""),
            "toko_id": data_reg.get("toko_id", 0),
            "status": data_reg.get("status", 1),
            "trash": data_reg.get("trash", 0),
            "jenis": data_reg.get("jenis", "POS"),
            "dtime_in": data_reg.get("dtime_in", now),
        }

        cursor.execute(
            "SELECT id FROM per_cabang_device WHERE machine_id = ? AND server_hash = ?",
            (machine_id, payload["server_hash"]),
        )
        exists = cursor.fetchone()

        if exists:
            cursor.execute(
                """
                UPDATE per_cabang_device SET
                    last_update = ?,
                    com_info = ?,
                    cpu_info = ?,
                    browser_verif = ?,
                    nama = ?,
                    alias = ?,
                    keterangan = ?,
                    cabang_id = ?,
                    cabang_nama = ?,
                    kelurahan = ?,
                    kecamatan = ?,
                    kabupaten = ?,
                    propinsi = ?,
                    alamat = ?,
                    toko_id = ?,
                    status = ?,
                    trash = ?,
                    jenis = ?,
                    dtime_in = ?,
                    server_hash = ?
                WHERE machine_id = ? AND server_hash = ?
                """,
                (
                    payload["last_update"],
                    payload["com_info"],
                    payload["cpu_info"],
                    payload["browser_verif"],
                    payload["nama"],
                    payload["alias"],
                    payload["keterangan"],
                    payload["cabang_id"],
                    payload["cabang_nama"],
                    payload["kelurahan"],
                    payload["kecamatan"],
                    payload["kabupaten"],
                    payload["propinsi"],
                    payload["alamat"],
                    payload["toko_id"],
                    payload["status"],
                    payload["trash"],
                    payload["jenis"],
                    payload["dtime_in"],
                    payload["server_hash"],
                    machine_id,
                    payload["server_hash"],
                ),
            )
        else:
            cursor.execute(
                """
                INSERT INTO per_cabang_device (
                    last_update, machine_id, com_info, cpu_info, browser_verif, nama, alias, keterangan,
                    cabang_id, cabang_nama, kelurahan, kecamatan, kabupaten, propinsi, alamat, toko_id,
                    status, trash, jenis, dtime_in, server_hash
                )
                VALUES (
                    ?, ?, ?, ?, ?, ?, ?, ?,
                    ?, ?, ?, ?, ?, ?, ?, ?,
                    ?, ?, ?, ?, ?
                )
                """,
                (
                    payload["last_update"],
                    payload["machine_id"],
                    payload["com_info"],
                    payload["cpu_info"],
                    payload["browser_verif"],
                    payload["nama"],
                    payload["alias"],
                    payload["keterangan"],
                    payload["cabang_id"],
                    payload["cabang_nama"],
                    payload["kelurahan"],
                    payload["kecamatan"],
                    payload["kabupaten"],
                    payload["propinsi"],
                    payload["alamat"],
                    payload["toko_id"],
                    payload["status"],
                    payload["trash"],
                    payload["jenis"],
                    payload["dtime_in"],
                    payload["server_hash"],
                ),
            )

        conn.commit()
        conn.close()
        return True
    except _DB_EXCEPTIONS as e:
        LOGGER.warning("Error simpan device lokal: %s", e)
        return False

def simpan_device_pending_lokal(data_reg, device_id=None):
    if not isinstance(data_reg, dict):
        return False
    data = dict(data_reg)
    data["status"] = 0
    return simpan_device_lokal(data, device_id=device_id)

def hapus_device_lokal(machine_id, server_hash=None):
    try:
        from pypos.core.utils.path_utils import get_db_path
        db_path = get_db_path()
        conn = connect_sqlite(db_path)
        conn.execute("PRAGMA foreign_keys = ON")
        _ensure_device_server_hash_column(conn)
        cursor = conn.cursor()
        if server_hash is None:
            server_hash = get_current_server_hash()
        cursor.execute(
            "DELETE FROM per_cabang_device WHERE machine_id = ? AND server_hash = ?",
            (str(machine_id), server_hash),
        )
        cursor.execute(
            "DELETE FROM per_cabang_device WHERE machine_id = ? AND (server_hash IS NULL OR server_hash = '')",
            (str(machine_id),),
        )
        conn.commit()
        conn.close()
        return True
    except _DB_EXCEPTIONS as e:
        LOGGER.warning("Error hapus device lokal: %s", e)
        return False

def _fetch_one_dict(cursor):
    row = cursor.fetchone()
    if not row:
        return None
    cols = [d[0] for d in cursor.description]
    return dict(zip(cols, row))

def cek_device_pending(device_id):
    try:
        from pypos.core.utils.path_utils import get_db_path
        db_path = get_db_path()
        conn = connect_sqlite(db_path)
        conn.execute("PRAGMA foreign_keys = ON")
        _ensure_device_server_hash_column(conn)
        cursor = conn.cursor()
        server_hash = get_current_server_hash()
        cursor.execute(
            "SELECT * FROM per_cabang_device WHERE machine_id = ? AND status = 0 AND trash = 0 AND server_hash = ?",
            (device_id, server_hash),
        )
        row = _fetch_one_dict(cursor)
        if not row:
            # edited by glg
            # Fallback legacy row (tanpa server_hash) agar status pending tetap terbaca.
            cursor.execute(
                "SELECT * FROM per_cabang_device "
                "WHERE machine_id = ? AND status = 0 AND trash = 0 "
                "AND (server_hash IS NULL OR server_hash = '')",
                (device_id,),
            )
            row = _fetch_one_dict(cursor)
            if row:
                try:
                    cursor.execute(
                        "UPDATE per_cabang_device SET server_hash = ? "
                        "WHERE machine_id = ? AND status = 0 AND trash = 0 "
                        "AND (server_hash IS NULL OR server_hash = '')",
                        (server_hash, device_id),
                    )
                    conn.commit()
                except sqlite3.Error as e:
                    LOGGER.warning("Gagal migrasi server_hash pending legacy: %s", e)
        conn.close()
        return row
    except _DB_EXCEPTIONS as e:
        LOGGER.warning("Error cek device pending: %s", e)
        return None

def kirim_device_pending_lokal(device_id):
    row = cek_device_pending(device_id)
    if not row:
        return None

    payload = {
        "machine_id": row.get("machine_id"),
        "browser_verif": row.get("browser_verif") or str(int(datetime.datetime.now().timestamp())),
        "keterangan": row.get("keterangan", ""),
        "cabang_nama": row.get("cabang_nama", ""),
        "cabang_id": row.get("cabang_id", 0),
        "cpu_info": row.get("cpu_info", ""),
        "com_info": row.get("com_info", ""),
        "alias": row.get("alias", ""),
        "nama_pengguna": row.get("nama", ""),
    }
    return post_device_registration(payload)

def get_device_id():
    return str(uuid.getnode())

def get_active_device_info(device_id=None):
    try:
        from pypos.core.utils.path_utils import get_db_path
        db_path = get_db_path()
        conn = connect_sqlite(db_path)
        conn.execute("PRAGMA foreign_keys = ON")
        _ensure_device_server_hash_column(conn)
        cursor = conn.cursor()
        if not device_id:
            device_id = get_device_id()
        server_hash = get_current_server_hash()

        # edited by glg
        # Ambil row device aktif secara penuh agar context transaksi punya toko_id/cabang_nama yang valid.
        def _fetch_active_row(sql_text, sql_params):
            cursor.execute(sql_text, sql_params)
            fetched_row = cursor.fetchone()
            columns = [desc[0] for desc in (cursor.description or [])]
            return fetched_row, columns

        row, columns = _fetch_active_row(
            "SELECT * FROM per_cabang_device WHERE machine_id = ? AND status = 1 AND trash = 0 AND server_hash = ?",
            (device_id, server_hash),
        )
        if not row:
            legacy_row, legacy_columns = _fetch_active_row(
                "SELECT * FROM per_cabang_device "
                "WHERE machine_id = ? AND status = 1 AND trash = 0 "
                "AND (server_hash IS NULL OR server_hash = '')",
                (device_id,),
            )
            if legacy_row:
                try:
                    cursor.execute(
                        "UPDATE per_cabang_device SET server_hash = ? "
                        "WHERE machine_id = ? AND status = 1 AND trash = 0 "
                        "AND (server_hash IS NULL OR server_hash = '')",
                        (server_hash, device_id),
                    )
                    conn.commit()
                except sqlite3.Error as e:
                    LOGGER.warning("Gagal migrasi server_hash legacy: %s", e)
                row = legacy_row
                columns = legacy_columns
        conn.close()
        if not row:
            return None
        row_dict = {}
        for idx, col in enumerate(columns):
            row_dict[col] = row[idx] if idx < len(row) else None
        return {
            "machine_id": row_dict.get("machine_id"),
            "cabang_id": row_dict.get("cabang_id"),
            "toko_id": row_dict.get("toko_id"),
            "cabang_nama": row_dict.get("cabang_nama"),
            "toko_nama": row_dict.get("toko_nama"),
        }
    except _DB_EXCEPTIONS as e:
        LOGGER.warning("Error ambil device aktif: %s", e)
        return None

def get_ip_address():
    try:
        return socket.gethostbyname(socket.gethostname())
    except OSError as e:
        LOGGER.warning("Gagal ambil IP address lokal: %s", e)
        # edited by glg
        # Ini sentinel fallback nilai IP, bukan bind socket.
        return "0.0.0.0"  # nosec B104


def get_os_info():
    return platform.system() + " " + platform.release()


def get_com_info():
    return platform.node()


def get_cpu_info():
    return platform.processor()


def get_browser_verif():
    return "desktop"


def get_gps_location():
    try:
        cfg = read_endpoint_config() or {}
        geo_url = str(cfg.get("ep_ip_geolocate") or "").strip()
        if not geo_url:
            return ""
        res = get_with_retry(geo_url, timeout=5)
        if res.status_code == 200:
            data = res.json()
            return data.get("loc", "")
    except (requests.exceptions.RequestException, _JSON_PARSE_EXCEPTIONS, ValueError) as e:
        LOGGER.warning("Gagal ambil lokasi GPS/ipinfo: %s", e)
        return ""
    return ""


def cek_device_terdaftar(device_id):

    from pypos.core.utils.path_utils import get_db_path

    DB_PATH = get_db_path()
    conn = connect_sqlite(DB_PATH)
    conn.execute("PRAGMA foreign_keys = ON")
    cursor = conn.cursor()

    _ensure_device_server_hash_column(conn)
    server_hash = get_current_server_hash()
    cursor.execute(
        "SELECT * FROM per_cabang_device WHERE machine_id = ? AND status = 1 AND trash = 0 AND server_hash = ?",
        (device_id, server_hash),
    )

    result = cursor.fetchone()
    if not result:
        cursor.execute(
            "SELECT * FROM per_cabang_device "
            "WHERE machine_id = ? AND status = 1 AND trash = 0 "
            "AND (server_hash IS NULL OR server_hash = '')",
            (device_id,),
        )
        result = cursor.fetchone()
        if result:
            try:
                cursor.execute(
                    "UPDATE per_cabang_device SET server_hash = ? "
                    "WHERE machine_id = ? AND status = 1 AND trash = 0 "
                    "AND (server_hash IS NULL OR server_hash = '')",
                    (server_hash, device_id),
                )
                conn.commit()
            except sqlite3.Error as e:
                LOGGER.warning("Gagal migrasi server_hash legacy: %s", e)
    conn.close()
    return result


def _ensure_device_server_hash_column(conn):
    try:
        cur = conn.cursor()
        cur.execute("PRAGMA table_info(per_cabang_device)")
        cols = [row[1] for row in cur.fetchall()]
        if "server_hash" not in cols:
            cur.execute("ALTER TABLE per_cabang_device ADD COLUMN server_hash TEXT")
    except sqlite3.Error:
        pass


def simpan_device_baru(
    device_id,
    alias,
    cabang_id,
    cabang_nama,
    nama,
    keterangan='',
    kelurahan='',
    kecamatan='',
    kabupaten='',
    propinsi='',
    alamat='',
    toko_id=None
):
    resolved_toko_id = _resolve_toko_id(payload={"toko_id": toko_id}, cfg=read_config() or {})
    payload = {
        "machine_id": device_id,
        "browser_verif": get_browser_verif(),
        "keterangan": keterangan,
        "cabang_nama": cabang_nama,
        "cabang_id": cabang_id,
        "cpu_info": get_cpu_info(),
        "com_info": get_com_info(),
        "alias": alias,
        "nama_pengguna": nama,
        "nama": nama,
        "kelurahan": kelurahan,
        "kecamatan": kecamatan,
        "kabupaten": kabupaten,
        "propinsi": propinsi,
        "alamat": alamat,
        "toko_id": resolved_toko_id,
    }
    result = post_device_registration(payload)
    status = None
    if isinstance(result, dict):
        status = result.get("status") or result.get("code") or result.get("result")
    else:
        status = result

    ok = str(status).lower() in ("1", "200", "true", "success", "ok", "berhasil")
    if ok:
        data_reg = None
        if isinstance(result, dict) and isinstance(result.get("data"), dict):
            data_reg = result.get("data")
        if not data_reg:
            data_reg = payload
        simpan_device_lokal(data_reg, device_id=device_id)
        return True

    simpan_device_pending_lokal(payload, device_id=device_id)
    return False

