"""
WMT (Workstation Management Terminal) configuration API
Handles config distribution and device update requests from WMT clients.
"""
from flask import Blueprint, request, jsonify, send_file
from datetime import datetime
from pathlib import Path
import json
from app.models import WMTGlobalConfig, Device, WMTUpdateRequest
from config.database_config import get_db
import logging

logger = logging.getLogger(__name__)

wmt_api_bp = Blueprint('wmt_api', __name__, url_prefix='/api/wmt')


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _get_or_create_global_config(session):
    """Return the single WMTGlobalConfig row, creating it with defaults if absent."""
    cfg = session.query(WMTGlobalConfig).first()
    if cfg is None:
        cfg = WMTGlobalConfig()
        session.add(cfg)
        session.flush()
    return cfg


def _latest_config_ts(session, mac_address):
    """Return timestamps for global config and this device's admin-reviewed info."""
    global_cfg = session.query(WMTGlobalConfig).first()
    global_ts = global_cfg.updated_at if global_cfg and global_cfg.updated_at else datetime(1970, 1, 1)

    device = session.query(Device).filter_by(mac_address=mac_address).first()
    # Use info_reviewed_at as the authoritative device-level timestamp
    device_ts = device.info_reviewed_at if device and device.info_reviewed_at else datetime(1970, 1, 1)

    latest = max(global_ts, device_ts)
    return global_ts, device_ts, latest


def _mark_wmt_checkin(device):
    """Stamp a device as having checked in via the WMT client API.

    Keeps the unified device record in sync: both the general monitoring
    'last_seen' and the WMT-specific 'wmt_last_seen' are advanced on every
    client interaction so the unified devices page reflects live state.
    """
    if device is None:
        return
    now = datetime.utcnow()
    device.last_seen = now
    device.wmt_last_seen = now


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------

@wmt_api_bp.route('/config/timestamp', methods=['GET'])
def get_config_timestamp():
    """
    Returns the last-modified timestamps for global config and this device's config.
    Query param: mac=<mac_address>

    Response:
    {
        "global_updated_at": "2026-04-22T10:00:00",
        "device_updated_at": "2026-04-22T09:00:00",   // null if device unknown
        "latest_updated_at": "2026-04-22T10:00:00"
    }
    """
    mac = request.args.get('mac', '').strip().lower()
    if not mac:
        return jsonify({'error': 'mac query parameter is required'}), 400

    try:
        with get_db().get_session() as session:
            global_ts, device_info_reviewed_ts, latest = _latest_config_ts(session, mac)

            # Stamp WMT check-in so the unified device record stays live
            device = session.query(Device).filter_by(mac_address=mac).first()
            _mark_wmt_checkin(device)

            return jsonify({
                'global_updated_at': global_ts.isoformat() if global_ts != datetime(1970, 1, 1) else None,
                'device_info_reviewed_at': device_info_reviewed_ts.isoformat() if device_info_reviewed_ts != datetime(1970, 1, 1) else None,
                'latest_updated_at': latest.isoformat(),
            }), 200
    except Exception as e:
        logger.error(f'Error getting WMT config timestamp: {e}')
        return jsonify({'error': str(e)}), 500


@wmt_api_bp.route('/config/<mac_address>', methods=['GET'])
def get_device_config(mac_address):
    """
    Returns merged config (global settings + device-specific) for a given MAC.
    Used by WMT client to pull updated config at startup.

    Response: merged dict consumable by the WMT config.txt writer.
    """
    mac = mac_address.strip().lower()
    try:
        with get_db().get_session() as session:
            global_cfg = _get_or_create_global_config(session)
            device = session.query(Device).filter_by(mac_address=mac).first()

            # Update last_seen / wmt_last_seen if device is known
            if device:
                _mark_wmt_checkin(device)

            _, device_ts, latest_ts = _latest_config_ts(session, mac)

            payload = {
                # Global settings (device custom_chrome_url overrides global chrome_url if set)
                'chrome_url': (device.custom_chrome_url if device and device.custom_chrome_url else global_cfg.chrome_url),
                'chrome_local_url': global_cfg.chrome_local_url or '',
                'chrome_insecure_origin': global_cfg.chrome_insecure_origin,
                'card_api_base_url': global_cfg.card_api_base_url,
                'server_log_url': global_cfg.server_log_url,
                'internet_check_host': global_cfg.internet_check_host,
                'update_host': global_cfg.update_host,
                'update_user': global_cfg.update_user,
                # Device-specific settings (empty string if unknown)
                'device_name': device.device_name if device else '',
                'hostname': device.hostname if device else '',
                'device_ip': device.device_ip if device else '',
                'location': device.location if device else '',
                'card_presence': device.card_presence if device else 'enable',
                # Admin-review timestamp for device info (client stores in [device] section)
                'info_reviewed_at': device.info_reviewed_at.isoformat() if (device and device.info_reviewed_at) else '1970-01-01T00:00:00',
                # Sync metadata
                'config_updated_at': latest_ts.isoformat(),
            }
            return jsonify(payload), 200
    except Exception as e:
        logger.error(f'Error fetching WMT config for {mac}: {e}')
        return jsonify({'error': str(e)}), 500


@wmt_api_bp.route('/config/update_request', methods=['POST'])
def submit_update_request():
    """
    WMT client sends its current running config so the server can validate it.
    Three outcomes:

    1. Device known AND config matches server record
       → record config_synced_at = now, update last_seen
       → respond {"status": "ok", "in_sync": true}
       Client does nothing.

    2. Device known BUT config differs from server record
       → the server is authoritative; client must pull fresh config
       → respond {"status": "sync_required", "in_sync": false}
       Client should call GET /api/wmt/config/<mac> to obtain correct values.

    3. Device unknown (new client – not registered by MAC or hostname)
       → create WMTUpdateRequest so admin can approve and assign settings
       → respond {"status": "pending_approval"}
       Client waits; it will get real config once admin approves.

    card_presence is always applied directly – no approval required.

    Expected JSON:
    {
        "mac_address": "b8:27:eb:aa:bb:cc",
        "device_name": "Masa-01",
        "hostname": "rpi-masa01",
        "device_ip": "192.168.1.100",
        "card_presence": "enable",
        "client_config_mtime": "2026-04-22T09:30:00"
    }
    """
    if not request.is_json:
        return jsonify({'error': 'Content-Type must be application/json'}), 400

    data = request.get_json()
    mac = (data.get('mac_address') or '').strip().lower()
    if not mac:
        return jsonify({'error': 'mac_address is required'}), 400

    proposed_name     = (data.get('device_name') or '').strip()
    proposed_hostname = (data.get('hostname')     or '').strip()
    proposed_ip       = (data.get('device_ip')    or '').strip()
    card_presence     = data.get('card_presence')

    def _eq(a, b):
        return (a or '') == (b or '')

    try:
        with get_db().get_session() as session:
            device = session.query(Device).filter_by(mac_address=mac).first()

            # ── Outcome 3: unknown device ─────────────────────────────
            if not device:
                # Devices with work_place=notconfig are unconfigured and must not
                # be registered. Route them to their own "to_be_configured" bucket.
                if not proposed_name or proposed_name.lower() == 'notconfig':
                    existing_tbc = (
                        session.query(WMTUpdateRequest)
                        .filter_by(mac_address=mac, status='to_be_configured')
                        .order_by(WMTUpdateRequest.submitted_at.desc())
                        .first()
                    )
                    if existing_tbc:
                        existing_tbc.submitted_at       = datetime.utcnow()
                        existing_tbc.proposed_hostname  = proposed_hostname or existing_tbc.proposed_hostname
                        existing_tbc.proposed_device_ip = proposed_ip       or existing_tbc.proposed_device_ip
                        logger.debug(f'WMT notconfig heartbeat refreshed for {mac}')
                    else:
                        req = WMTUpdateRequest(
                            mac_address=mac,
                            device_id=None,
                            proposed_device_name=None,
                            proposed_hostname=proposed_hostname or None,
                            proposed_device_ip=proposed_ip or None,
                            client_config_mtime=data.get('client_config_mtime'),
                            submitted_at=datetime.utcnow(),
                            status='to_be_configured',
                        )
                        session.add(req)
                        logger.info(f'WMT unconfigured device queued: {proposed_hostname} / {mac}')
                    return jsonify({
                        'status': 'to_be_configured',
                        'message': 'Device has no work_place set. Awaiting admin assignment.',
                    }), 202

                # Check if a pending request with the same data already exists
                existing = (
                    session.query(WMTUpdateRequest)
                    .filter_by(mac_address=mac, status='pending')
                    .order_by(WMTUpdateRequest.submitted_at.desc())
                    .first()
                )
                if existing and (
                    _eq(existing.proposed_device_name, proposed_name) and
                    _eq(existing.proposed_hostname,    proposed_hostname) and
                    _eq(existing.proposed_device_ip,   proposed_ip)
                ):
                    existing.submitted_at = datetime.utcnow()
                    logger.debug(f'WMT unknown device {mac} – refreshed existing pending request')
                else:
                    req = WMTUpdateRequest(
                        mac_address=mac,
                        device_id=None,
                        proposed_device_name=proposed_name or None,
                        proposed_hostname=proposed_hostname or None,
                        proposed_device_ip=proposed_ip or None,
                        client_config_mtime=data.get('client_config_mtime'),
                        submitted_at=datetime.utcnow(),
                        status='pending',
                    )
                    session.add(req)
                    logger.info(f'WMT pending approval request created for unknown device {mac}')

                return jsonify({
                    'status': 'pending_approval',
                    'message': 'Device not registered. Awaiting admin approval.',
                }), 202

            # Device is known from here on ─────────────────────────────
            device.last_seen = datetime.utcnow()
            _mark_wmt_checkin(device)
            if card_presence in ('enable', 'disable'):
                device.card_presence = card_presence

            # ── Outcome 1: config matches server record ────────────────
            config_in_sync = (
                _eq(proposed_name,     device.nume_masa)  and
                _eq(proposed_hostname, device.hostname)   and
                _eq(proposed_ip,       device.device_ip)
            )

            if config_in_sync:
                device.config_synced_at = datetime.utcnow()
                logger.debug(f'WMT config OK for {mac} – synced_at updated')
                return jsonify({
                    'status': 'ok',
                    'in_sync': True,
                    'message': 'Config matches server record.',
                }), 200

            # ── Outcome 2: config differs – client must pull from server ─
            logger.info(
                f'WMT config mismatch for {mac}: '
                f'client has name={proposed_name!r} host={proposed_hostname!r} ip={proposed_ip!r} '
                f'but server has name={device.nume_masa!r} host={device.hostname!r} ip={device.device_ip!r}'
            )
            return jsonify({
                'status': 'sync_required',
                'in_sync': False,
                'message': 'Config differs from server record. Pull updated config.',
            }), 200

    except Exception as e:
        logger.error(f'Error processing WMT config check from {mac}: {e}')
        return jsonify({'error': str(e)}), 500


# ---------------------------------------------------------------------------
# WMT client auto-update endpoints
# ---------------------------------------------------------------------------

WMT_RELEASES_DIR = Path('data/wmt_releases')


@wmt_api_bp.route('/client/version', methods=['GET'])
def get_client_version():
    """
    Returns the latest available WMT client version metadata.
    Client calls this to decide if it needs to update.

    Response: { "version": "3.0", "notes": "...", "uploaded_at": "...", "filename": "wmt_v3.0.zip" }
    """
    meta_path = WMT_RELEASES_DIR / 'latest.json'
    if not meta_path.exists():
        return jsonify({'error': 'No release available'}), 404
    try:
        meta = json.loads(meta_path.read_text())
        return jsonify(meta), 200
    except Exception as e:
        logger.error(f'Error reading WMT release metadata: {e}')
        return jsonify({'error': str(e)}), 500


@wmt_api_bp.route('/client/download', methods=['GET'])
def download_client_release():
    """
    Streams the latest WMT client release zip to the requesting device.
    Client downloads this when its version is older than the server version.
    """
    meta_path = WMT_RELEASES_DIR / 'latest.json'
    if not meta_path.exists():
        return jsonify({'error': 'No release available'}), 404
    try:
        meta = json.loads(meta_path.read_text())
        zip_path = WMT_RELEASES_DIR / meta['filename']
        if not zip_path.exists():
            return jsonify({'error': f'Release file not found: {meta["filename"]}'}), 404
        logger.info(f'WMT client downloading release {meta["version"]} from {request.remote_addr}')
        return send_file(
            str(zip_path.resolve()),
            mimetype='application/zip',
            as_attachment=True,
            download_name=meta['filename'],
        )
    except Exception as e:
        logger.error(f'Error serving WMT release: {e}')
        return jsonify({'error': str(e)}), 500
