# routes/biometric.py
from __future__ import annotations

import json
from datetime import datetime, date, time, timedelta

import requests
from flask import current_app
from flask_login import current_user

from extensions import db
from models import Employee, BiometricPunch, BiometricSyncRun, SystemSetting,BiometricEmployeeMap


# --- Settings helper (same idea as in admin.brand_config) -------------------

def get_json_setting(key: str, default):
    setting = SystemSetting.query.filter_by(key=key).first()
    if setting and getattr(setting, "json_value", None):
        try:
            return json.loads(setting.json_value)
        except Exception:
            current_app.logger.exception("Invalid JSON for setting %s", key)
            return default
    return default


def _resolve_list(data, path: str):
    """Resolve nested list by dot path like 'Data' or 'data.records'."""
    if not path:
        return data if isinstance(data, list) else []

    parts = [p for p in path.split('.') if p]
    cur = data
    for p in parts:
        if isinstance(cur, dict):
            cur = cur.get(p)
        else:
            return []
    return cur if isinstance(cur, list) else []


# ---------------------------------------------------------------------------
# PUBLIC: sync a single date
# ---------------------------------------------------------------------------

def sync_biometric_for_date(target_date: date, source_name: str = "default") -> BiometricSyncRun:
    """
    Fetch biometric data for a single date based on biometric_settings
    and store in BiometricPunch, grouped under BiometricSyncRun.

    Returns the BiometricSyncRun row.
    """
    run = BiometricSyncRun(
        sync_date=target_date,
        source_type="api",  # will be overwritten if DB
        source_name=source_name,
        status="running",
        started_at=datetime.utcnow(),
    )
    try:
        # Optional: who triggered the sync (if logged-in context exists)
        try:
            if current_user and getattr(current_user, "id", None):
                run.triggered_by_id = current_user.id
        except Exception:
            pass

        db.session.add(run)
        db.session.flush()  # ensure run.id

        cfg = get_json_setting("biometric_settings", {})
        if not cfg or not cfg.get("enabled"):
            run.status = "skipped"
            run.message = "Biometric integration disabled."
            run.ended_at = datetime.utcnow()
            db.session.commit()
            return run

        source_type = (cfg.get("source_type") or "api").lower()
        run.source_type = source_type

        if source_type == "api":
            count = _sync_biometric_from_api(cfg, target_date, run)
        elif source_type == "db":
            count = _sync_biometric_from_db(cfg, target_date, run)
        else:
            raise ValueError(f"Unsupported biometric source_type: {source_type}")

        run.status = "success"
        run.punches_saved = count
        run.message = f"Biometric sync OK, punches stored: {count}"
        run.ended_at = datetime.utcnow()
        db.session.commit()
        return run

    except Exception as e:
        current_app.logger.exception("Biometric sync failed for %s", target_date)
        db.session.rollback()
        run.status = "error"
        run.message = f"Error: {e}"
        run.ended_at = datetime.utcnow()
        db.session.add(run)
        db.session.commit()
        return run


# ---------------------------------------------------------------------------
# API-based sync (your current case)
# ---------------------------------------------------------------------------

def _sync_biometric_from_api(cfg: dict, target_date: date, run: BiometricSyncRun) -> int:
    api_cfg = cfg.get("api", {}) or {}

    base_url = (api_cfg.get("base_url") or "").strip()
    http_method = (api_cfg.get("api_http_method") or api_cfg.get("http_method") or "GET").upper()
    req_tmpl = (api_cfg.get("api_request_template") or api_cfg.get("request_template") or "").strip()

    if not base_url:
        raise ValueError("Biometric API base_url is not configured.")

    date_str = target_date.strftime("%Y-%m-%d")
    query_part = req_tmpl.format(date=date_str) if req_tmpl else ""
    if query_part and not query_part.startswith(("?", "&")):
        query_part = "?" + query_part

    url = base_url.rstrip("/") + query_part

    headers_json = api_cfg.get("api_headers_json") or api_cfg.get("headers_json") or ""
    try:
        headers = json.loads(headers_json) if headers_json.strip() else {}
    except Exception:
        headers = {}
        current_app.logger.warning("Invalid biometric API headers_json; ignoring.")

    current_app.logger.info("Biometric API call: %s %s", http_method, url)

    if http_method == "POST":
        resp = requests.post(url, headers=headers, timeout=30)
    else:
        resp = requests.get(url, headers=headers, timeout=30)

    resp.raise_for_status()
    payload = resp.json()

    list_key = api_cfg.get("api_response_list_key") or api_cfg.get("response_list_key") or "Data"
    rows = _resolve_list(payload, list_key)

    if not isinstance(rows, list):
        current_app.logger.warning("Biometric API list resolved to non-list.")
        return 0

    # Mapping keys - default to your current API
    map_emp_code = api_cfg.get("api_map_employee_code") or api_cfg.get("map_employee_code") or "card_number"
    map_in_time = api_cfg.get("api_map_in_time") or api_cfg.get("map_in_time") or "in_time"
    map_out_time = api_cfg.get("api_map_out_time") or api_cfg.get("map_out_time") or "out_time"
    map_device = api_cfg.get("api_map_device_code") or api_cfg.get("map_device_code") or "Device_Code"
    time_fmt = api_cfg.get("api_time_format") or api_cfg.get("time_format") or "%Y-%m-%dT%H:%M:%S"

    # Clean old punches for this date + source
    start_dt = datetime.combine(target_date, time.min)
    end_dt = datetime.combine(target_date + timedelta(days=1), time.min)

    BiometricPunch.query.filter(
        BiometricPunch.punch_time >= start_dt,
        BiometricPunch.punch_time < end_dt,
        BiometricPunch.source_name == run.source_name,
        BiometricPunch.source_type == "api",
    ).delete(synchronize_session=False)
    db.session.flush()

    total_saved = 0

    for row in rows:
        if not isinstance(row, dict):
            continue

        ext_code = str(row.get(map_emp_code) or "").strip()
        if not ext_code:
            continue

        employee_id = None
        mapping = BiometricEmployeeMap.query.filter_by(
            external_code=ext_code,
            source_name=run.source_name,
            is_active=True
        ).first()

        if mapping:
            employee_id = mapping.employee_id
        else:
            emp = Employee.query.filter_by(employee_code=ext_code).first()
            if emp:
                employee_id = emp.id

        device_code = str(row.get(map_device) or "").strip() or None

        in_raw = row.get(map_in_time)
        out_raw = row.get(map_out_time)

        # IN
        if in_raw:
            try:
                dt_in = datetime.strptime(in_raw, time_fmt)
                db.session.add(BiometricPunch(
                    sync_run_id=run.id,
                    employee_id=employee_id,
                    external_employee_code=ext_code,
                    punch_time=dt_in,
                    direction="in",
                    device_code=device_code,
                    source_type="api",
                    source_name=run.source_name,
                    raw_data=json.dumps(row),
                ))
                total_saved += 1
            except Exception:
                current_app.logger.exception("Failed to parse IN time %s", in_raw)

        # OUT
        if out_raw and out_raw != in_raw:
            try:
                dt_out = datetime.strptime(out_raw, time_fmt)
                db.session.add(BiometricPunch(
                    sync_run_id=run.id,
                    employee_id=employee_id,
                    external_employee_code=ext_code,
                    punch_time=dt_out,
                    direction="out",
                    device_code=device_code,
                    source_type="api",
                    source_name=run.source_name,
                    raw_data=json.dumps(row),
                ))
                total_saved += 1
            except Exception:
                current_app.logger.exception("Failed to parse OUT time %s", out_raw)

    db.session.commit()
    return total_saved


# ---------------------------------------------------------------------------
# DB-based sync stub (future)
# ---------------------------------------------------------------------------

def _sync_biometric_from_db(cfg: dict, target_date: date, run: BiometricSyncRun) -> int:
    """
    Placeholder for DB-based sync. When you plug in an actual biometric DB,
    SELECT rows for target_date, map to BiometricPunch similarly to API case.
    """
    current_app.logger.warning("DB-based biometric sync not implemented yet.")
    return 0

def get_biometric_day_summary(employee_id: int, target_date: date, source_name: str = "default"):
    """
    Returns a dict with first IN, last OUT and total hours for a given employee/date
    based on BiometricPunch.
    """
    start_dt = datetime.combine(target_date, time.min)
    end_dt = datetime.combine(target_date + timedelta(days=1), time.min)

    base_q = BiometricPunch.query.filter(
        BiometricPunch.employee_id == employee_id,
        BiometricPunch.punch_time >= start_dt,
        BiometricPunch.punch_time < end_dt,
    )

    # filter by source if needed
    if source_name:
        base_q = base_q.filter(BiometricPunch.source_name == source_name)

    first_in = (base_q.filter(BiometricPunch.direction == "in")
                      .order_by(BiometricPunch.punch_time.asc())
                      .first())
    last_out = (base_q.filter(BiometricPunch.direction == "out")
                      .order_by(BiometricPunch.punch_time.desc())
                      .first())

    check_in = first_in.punch_time if first_in else None
    check_out = last_out.punch_time if last_out else None
    total_hours = None

    if check_in and check_out and check_out > check_in:
        total_hours = (check_out - check_in).total_seconds() / 3600.0

    return {
        "check_in": check_in,
        "check_out": check_out,
        "total_hours": total_hours,
        "has_data": bool(first_in or last_out),
    }