"""Printers API routes - new Asset-based architecture."""

import logging

from flask import Blueprint, request
from flask_jwt_extended import jwt_required

from shopdb.extensions import db, cache
from shopdb.core.models import Asset, AssetType, Vendor, Model, Communication, CommunicationType
from shopdb.utils.responses import (
    success_response,
    error_response,
    paginated_response,
    ErrorCodes
)
from shopdb.utils.pagination import get_pagination_params, paginate_query

from ..models import Printer, PrinterType
from ..services import ZabbixService

logger = logging.getLogger(__name__)

printers_asset_bp = Blueprint('printers_asset', __name__)


# =============================================================================
# Printer Types
# =============================================================================

@printers_asset_bp.route('/types', methods=['GET'])
@jwt_required(optional=True)
def list_printer_types():
    """List all printer types."""
    page, per_page = get_pagination_params(request)

    query = PrinterType.query

    if request.args.get('active', 'true').lower() != 'false':
        query = query.filter(PrinterType.isactive == True)

    if search := request.args.get('search'):
        query = query.filter(PrinterType.printertype.ilike(f'%{search}%'))

    query = query.order_by(PrinterType.printertype)

    items, total = paginate_query(query, page, per_page)
    data = [t.to_dict() for t in items]

    return paginated_response(data, page, per_page, total)


@printers_asset_bp.route('/types/<int:type_id>', methods=['GET'])
@jwt_required(optional=True)
def get_printer_type(type_id: int):
    """Get a single printer type."""
    t = PrinterType.query.get(type_id)

    if not t:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Printer type with ID {type_id} not found',
            http_code=404
        )

    return success_response(t.to_dict())


@printers_asset_bp.route('/types', methods=['POST'])
@jwt_required()
def create_printer_type():
    """Create a new printer type."""
    data = request.get_json()

    if not data or not data.get('printertype'):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'printertype is required')

    if PrinterType.query.filter_by(printertype=data['printertype']).first():
        return error_response(
            ErrorCodes.CONFLICT,
            f"Printer type '{data['printertype']}' already exists",
            http_code=409
        )

    t = PrinterType(
        printertype=data['printertype'],
        description=data.get('description'),
        icon=data.get('icon')
    )

    db.session.add(t)
    db.session.commit()

    return success_response(t.to_dict(), message='Printer type created', http_code=201)


# =============================================================================
# Printers CRUD
# =============================================================================

@printers_asset_bp.route('', methods=['GET'])
@jwt_required(optional=True)
def list_printers():
    """
    List all printers with filtering and pagination.

    Query parameters:
    - page, per_page: Pagination
    - active: Filter by active status
    - search: Search by asset number, name, or hostname
    - type_id: Filter by printer type ID
    - vendor_id: Filter by vendor ID
    - location_id: Filter by location ID
    - businessunit_id: Filter by business unit ID
    """
    page, per_page = get_pagination_params(request)

    # Join Printer with Asset
    query = db.session.query(Printer).join(Asset)

    # Active filter
    if request.args.get('active', 'true').lower() != 'false':
        query = query.filter(Asset.isactive == True)

    # Search filter
    if search := request.args.get('search'):
        query = query.filter(
            db.or_(
                Asset.assetnumber.ilike(f'%{search}%'),
                Asset.name.ilike(f'%{search}%'),
                Asset.serialnumber.ilike(f'%{search}%'),
                Printer.hostname.ilike(f'%{search}%'),
                Printer.windowsname.ilike(f'%{search}%')
            )
        )

    # Type filter
    if type_id := request.args.get('type_id'):
        query = query.filter(Printer.printertypeid == int(type_id))

    # Vendor filter
    if vendor_id := request.args.get('vendor_id'):
        query = query.filter(Printer.vendorid == int(vendor_id))

    # Location filter
    if location_id := request.args.get('location_id'):
        query = query.filter(Asset.locationid == int(location_id))

    # Business unit filter
    if bu_id := request.args.get('businessunit_id'):
        query = query.filter(Asset.businessunitid == int(bu_id))

    # Sorting
    sort_by = request.args.get('sort', 'hostname')
    sort_dir = request.args.get('dir', 'asc')

    if sort_by == 'hostname':
        col = Printer.hostname
    elif sort_by == 'assetnumber':
        col = Asset.assetnumber
    elif sort_by == 'name':
        col = Asset.name
    else:
        col = Printer.hostname

    query = query.order_by(col.desc() if sort_dir == 'desc' else col)

    items, total = paginate_query(query, page, per_page)

    # Build response with both asset and printer data
    data = []
    for printer in items:
        item = printer.asset.to_dict() if printer.asset else {}
        item['printer'] = printer.to_dict()

        # Add primary IP address
        if printer.asset:
            primary_comm = Communication.query.filter_by(
                assetid=printer.asset.assetid,
                isprimary=True
            ).first()
            if not primary_comm:
                primary_comm = Communication.query.filter_by(
                    assetid=printer.asset.assetid
                ).first()
            item['ipaddress'] = primary_comm.ipaddress if primary_comm else None

        data.append(item)

    return paginated_response(data, page, per_page, total)


@printers_asset_bp.route('/<int:printer_id>', methods=['GET'])
@jwt_required(optional=True)
def get_printer(printer_id: int):
    """Get a single printer with full details."""
    printer = Printer.query.get(printer_id)

    if not printer:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Printer with ID {printer_id} not found',
            http_code=404
        )

    result = printer.asset.to_dict() if printer.asset else {}
    result['printer'] = printer.to_dict()

    # Add communications
    if printer.asset:
        comms = Communication.query.filter_by(assetid=printer.asset.assetid).all()
        result['communications'] = [c.to_dict() for c in comms]

    return success_response(result)


@printers_asset_bp.route('/by-asset/<int:asset_id>', methods=['GET'])
@jwt_required(optional=True)
def get_printer_by_asset(asset_id: int):
    """Get printer data by asset ID."""
    printer = Printer.query.filter_by(assetid=asset_id).first()

    if not printer:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Printer for asset {asset_id} not found',
            http_code=404
        )

    result = printer.asset.to_dict() if printer.asset else {}
    result['printer'] = printer.to_dict()

    return success_response(result)


@printers_asset_bp.route('', methods=['POST'])
@jwt_required()
def create_printer():
    """
    Create new printer (creates both Asset and Printer records).

    Required fields:
    - assetnumber: Business identifier

    Optional fields:
    - name, serialnumber, statusid, locationid, businessunitid
    - printertypeid, vendorid, modelnumberid, hostname
    - windowsname, sharename, iscsf, installpath, pin
    - iscolor, isduplex, isnetwork
    - mapx, mapy, notes
    """
    data = request.get_json()

    if not data:
        return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided')

    if not data.get('assetnumber'):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'assetnumber is required')

    # Check for duplicate assetnumber
    if Asset.query.filter_by(assetnumber=data['assetnumber']).first():
        return error_response(
            ErrorCodes.CONFLICT,
            f"Asset with number '{data['assetnumber']}' already exists",
            http_code=409
        )

    # Get printer asset type
    printer_type = AssetType.query.filter_by(assettype='printer').first()
    if not printer_type:
        return error_response(
            ErrorCodes.INTERNAL_ERROR,
            'Printer asset type not found. Plugin may not be properly installed.',
            http_code=500
        )

    # Create the core asset
    asset = Asset(
        assetnumber=data['assetnumber'],
        name=data.get('name'),
        serialnumber=data.get('serialnumber'),
        assettypeid=printer_type.assettypeid,
        statusid=data.get('statusid', 1),
        locationid=data.get('locationid'),
        businessunitid=data.get('businessunitid'),
        mapx=data.get('mapx'),
        mapy=data.get('mapy'),
        notes=data.get('notes')
    )

    db.session.add(asset)
    db.session.flush()  # Get the assetid

    # Create the printer extension
    printer = Printer(
        assetid=asset.assetid,
        printertypeid=data.get('printertypeid'),
        vendorid=data.get('vendorid'),
        modelnumberid=data.get('modelnumberid'),
        hostname=data.get('hostname'),
        windowsname=data.get('windowsname'),
        sharename=data.get('sharename'),
        iscsf=data.get('iscsf', False),
        installpath=data.get('installpath'),
        pin=data.get('pin'),
        iscolor=data.get('iscolor', False),
        isduplex=data.get('isduplex', False),
        isnetwork=data.get('isnetwork', True)
    )

    db.session.add(printer)

    # Create communication record if IP provided
    if data.get('ipaddress'):
        ip_comtype = CommunicationType.query.filter_by(comtype='IP').first()
        if ip_comtype:
            comm = Communication(
                assetid=asset.assetid,
                comtypeid=ip_comtype.comtypeid,
                ipaddress=data['ipaddress'],
                isprimary=True
            )
            db.session.add(comm)

    db.session.commit()

    result = asset.to_dict()
    result['printer'] = printer.to_dict()

    return success_response(result, message='Printer created', http_code=201)


@printers_asset_bp.route('/<int:printer_id>', methods=['PUT'])
@jwt_required()
def update_printer(printer_id: int):
    """Update printer (both Asset and Printer records)."""
    printer = Printer.query.get(printer_id)

    if not printer:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Printer with ID {printer_id} not found',
            http_code=404
        )

    data = request.get_json()
    if not data:
        return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided')

    asset = printer.asset

    # Check for conflicting assetnumber
    if 'assetnumber' in data and data['assetnumber'] != asset.assetnumber:
        if Asset.query.filter_by(assetnumber=data['assetnumber']).first():
            return error_response(
                ErrorCodes.CONFLICT,
                f"Asset with number '{data['assetnumber']}' already exists",
                http_code=409
            )

    # Update asset fields
    asset_fields = ['assetnumber', 'name', 'serialnumber', 'statusid',
                    'locationid', 'businessunitid', 'mapx', 'mapy', 'notes', 'isactive']
    for key in asset_fields:
        if key in data:
            setattr(asset, key, data[key])

    # Update printer fields
    printer_fields = ['printertypeid', 'vendorid', 'modelnumberid', 'hostname',
                      'windowsname', 'sharename', 'iscsf', 'installpath', 'pin',
                      'iscolor', 'isduplex', 'isnetwork']
    for key in printer_fields:
        if key in data:
            setattr(printer, key, data[key])

    db.session.commit()

    result = asset.to_dict()
    result['printer'] = printer.to_dict()

    return success_response(result, message='Printer updated')


@printers_asset_bp.route('/<int:printer_id>', methods=['DELETE'])
@jwt_required()
def delete_printer(printer_id: int):
    """Delete (soft delete) printer."""
    printer = Printer.query.get(printer_id)

    if not printer:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Printer with ID {printer_id} not found',
            http_code=404
        )

    # Soft delete the asset
    printer.asset.isactive = False
    db.session.commit()

    return success_response(message='Printer deleted')


# =============================================================================
# Supply Levels (Zabbix Integration)
# =============================================================================

@printers_asset_bp.route('/<int:printer_id>/supplies', methods=['GET'])
@jwt_required(optional=True)
def get_printer_supplies(printer_id: int):
    """Get supply levels from Zabbix (real-time lookup)."""
    printer = Printer.query.get(printer_id)

    if not printer:
        return error_response(ErrorCodes.NOT_FOUND, 'Printer not found', http_code=404)

    # Get IP address from communications
    comm = Communication.query.filter_by(
        assetid=printer.assetid,
        isprimary=True
    ).first()
    if not comm:
        comm = Communication.query.filter_by(assetid=printer.assetid).first()

    if not comm or not comm.ipaddress:
        return error_response(ErrorCodes.VALIDATION_ERROR, 'Printer has no IP address')

    service = ZabbixService()
    if not service.isconfigured or not service.isreachable:
        # Return empty supplies if Zabbix not available (fail gracefully)
        return success_response({
            'ipaddress': comm.ipaddress,
            'supplies': []
        })

    supplies = service.getsuppliesbyip(comm.ipaddress)

    return success_response({
        'ipaddress': comm.ipaddress,
        'supplies': supplies or []
    })


# =============================================================================
# Low Supplies
# =============================================================================

def _get_low_supplies_data():
    """Build low supplies data (cached for 10 minutes)."""
    cached = cache.get('printers_low_supplies')
    if cached is not None:
        return cached

    service = ZabbixService()
    if not service.isconfigured or not service.isreachable:
        return {'printers': [], 'summary': {'total_checked': 0, 'low': 0, 'critical': 0}}

    # All active printers with an IP address
    printers = (
        db.session.query(Printer, Asset, Communication)
        .join(Asset, Asset.assetid == Printer.assetid)
        .join(Communication, Communication.assetid == Asset.assetid)
        .filter(Asset.isactive == True)
        .filter(Communication.ipaddress.isnot(None))
        .filter(Communication.ipaddress != '')
        .all()
    )

    # Dedupe by printer id (may have multiple comms)
    seen = set()
    unique_printers = []
    for printer, asset, comm in printers:
        if printer.printerid not in seen:
            seen.add(printer.printerid)
            unique_printers.append((printer, asset, comm))

    results = []
    total_checked = 0

    for printer, asset, comm in unique_printers:
        supplies = service.getsuppliesbyip_cached(comm.ipaddress)
        if supplies is None:
            continue

        total_checked += 1

        # Annotate each supply with status
        annotated = []
        has_low = False
        for s in supplies:
            level = s.get('level', 0)
            if level <= 5:
                status = 'critical'
                has_low = True
            elif level <= 10:
                status = 'low'
                has_low = True
            else:
                status = 'ok'
            annotated.append({
                'name': s.get('name', 'Unknown'),
                'level': level,
                'status': status
            })

        if has_low:
            # Get location name
            location_name = None
            if asset.locationid:
                from shopdb.core.models import Location
                loc = Location.query.get(asset.locationid)
                if loc:
                    location_name = loc.location

            results.append({
                'printerid': printer.printerid,
                'printername': asset.name or printer.hostname or '',
                'assetnumber': asset.assetnumber or '',
                'ipaddress': comm.ipaddress,
                'location': location_name,
                'supplies': annotated
            })

    low_count = 0
    critical_count = 0
    for p in results:
        has_critical = any(s['status'] == 'critical' for s in p['supplies'])
        has_low = any(s['status'] == 'low' for s in p['supplies'])
        if has_critical:
            critical_count += 1
        elif has_low:
            low_count += 1

    data = {
        'printers': results,
        'summary': {
            'total_checked': total_checked,
            'low': low_count,
            'critical': critical_count
        }
    }

    cache.set('printers_low_supplies', data, timeout=600)
    return data


@printers_asset_bp.route('/lowsupplies', methods=['GET'])
@jwt_required(optional=True)
def low_supplies():
    """Get printers with low or critical supply levels."""
    data = _get_low_supplies_data()
    return success_response(data)


# =============================================================================
# Dashboard
# =============================================================================

@printers_asset_bp.route('/dashboard/summary', methods=['GET'])
@jwt_required(optional=True)
def dashboard_summary():
    """Get printer dashboard summary data."""
    # Total active printers
    total = db.session.query(Printer).join(Asset).filter(
        Asset.isactive == True
    ).count()

    # Count by printer type
    by_type = db.session.query(
        PrinterType.printertype,
        db.func.count(Printer.printerid)
    ).join(Printer, Printer.printertypeid == PrinterType.printertypeid
    ).join(Asset, Asset.assetid == Printer.assetid
    ).filter(Asset.isactive == True
    ).group_by(PrinterType.printertype
    ).all()

    # Count by vendor
    by_vendor = db.session.query(
        Vendor.vendor,
        db.func.count(Printer.printerid)
    ).join(Printer, Printer.vendorid == Vendor.vendorid
    ).join(Asset, Asset.assetid == Printer.assetid
    ).filter(Asset.isactive == True
    ).group_by(Vendor.vendor
    ).all()

    # Get real low/critical supply counts (skip if Zabbix not reachable)
    low_count = 0
    critical_count = 0
    service = ZabbixService()
    if service.isconfigured and service.isreachable:
        try:
            supply_data = _get_low_supplies_data()
            low_count = supply_data['summary']['low']
            critical_count = supply_data['summary']['critical']
        except Exception as e:
            logger.warning(f"Could not fetch supply data for dashboard: {e}")

    return success_response({
        'total': total,
        'totalprinters': total,
        'online': total,
        'lowsupplies': low_count,
        'criticalsupplies': critical_count,
        'bytype': [{'type': t, 'count': c} for t, c in by_type],
        'byvendor': [{'vendor': v, 'count': c} for v, c in by_vendor],
    })
