"""Computers plugin API endpoints."""

from flask import Blueprint, request
from flask_jwt_extended import jwt_required

from shopdb.extensions import db
from shopdb.core.models import Asset, AssetType, OperatingSystem, Application, AppVersion, AuditLog
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 Computer, ComputerType, ComputerInstalledApp

computers_bp = Blueprint('computers', __name__)


# =============================================================================
# Computer Types
# =============================================================================

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

    query = ComputerType.query

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

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

    query = query.order_by(ComputerType.computertype)

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

    return paginated_response(data, page, per_page, total)


@computers_bp.route('/types/<int:type_id>', methods=['GET'])
@jwt_required(optional=True)
def get_computer_type(type_id: int):
    """Get a single computer type."""
    t = ComputerType.query.get(type_id)

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

    return success_response(t.to_dict())


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

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

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

    t = ComputerType(
        computertype=data['computertype'],
        description=data.get('description'),
        icon=data.get('icon')
    )

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

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


@computers_bp.route('/types/<int:type_id>', methods=['PUT'])
@jwt_required()
def update_computer_type(type_id: int):
    """Update a computer type."""
    t = ComputerType.query.get(type_id)

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

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

    if 'computertype' in data and data['computertype'] != t.computertype:
        if ComputerType.query.filter_by(computertype=data['computertype']).first():
            return error_response(
                ErrorCodes.CONFLICT,
                f"Computer type '{data['computertype']}' already exists",
                http_code=409
            )

    for key in ['computertype', 'description', 'icon', 'isactive']:
        if key in data:
            setattr(t, key, data[key])

    db.session.commit()
    return success_response(t.to_dict(), message='Computer type updated')


# =============================================================================
# Computers CRUD
# =============================================================================

@computers_bp.route('', methods=['GET'])
@jwt_required(optional=True)
def list_computers():
    """
    List all computers 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 computer type ID
    - os_id: Filter by operating system ID
    - location_id: Filter by location ID
    - businessunit_id: Filter by business unit ID
    - shopfloor: Filter by shopfloor flag (true/false)
    """
    page, per_page = get_pagination_params(request)

    # Join Computer with Asset
    query = db.session.query(Computer).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}%'),
                Computer.hostname.ilike(f'%{search}%')
            )
        )

    # Computer type filter
    if type_id := request.args.get('type_id'):
        query = query.filter(Computer.computertypeid == int(type_id))

    # OS filter
    if os_id := request.args.get('os_id'):
        query = query.filter(Computer.osid == int(os_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))

    # Shopfloor filter
    if shopfloor := request.args.get('shopfloor'):
        query = query.filter(Computer.isshopfloor == (shopfloor.lower() == 'true'))

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

    if sort_by == 'hostname':
        col = Computer.hostname
    elif sort_by == 'assetnumber':
        col = Asset.assetnumber
    elif sort_by == 'name':
        col = Asset.name
    elif sort_by == 'lastreporteddate':
        col = Computer.lastreporteddate
    else:
        col = Computer.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 computer data
    data = []
    for comp in items:
        item = comp.asset.to_dict() if comp.asset else {}
        item['computer'] = comp.to_dict()
        data.append(item)

    return paginated_response(data, page, per_page, total)


@computers_bp.route('/<int:computer_id>', methods=['GET'])
@jwt_required(optional=True)
def get_computer(computer_id: int):
    """Get a single computer with full details."""
    comp = Computer.query.get(computer_id)

    if not comp:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Computer with ID {computer_id} not found',
            http_code=404
        )

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

    return success_response(result)


@computers_bp.route('/by-asset/<int:asset_id>', methods=['GET'])
@jwt_required(optional=True)
def get_computer_by_asset(asset_id: int):
    """Get computer data by asset ID."""
    comp = Computer.query.filter_by(assetid=asset_id).first()

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

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

    return success_response(result)


@computers_bp.route('/by-hostname/<hostname>', methods=['GET'])
@jwt_required(optional=True)
def get_computer_by_hostname(hostname: str):
    """Get computer by hostname."""
    comp = Computer.query.filter_by(hostname=hostname).first()

    if not comp:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Computer with hostname {hostname} not found',
            http_code=404
        )

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

    return success_response(result)


@computers_bp.route('', methods=['POST'])
@jwt_required()
def create_computer():
    """
    Create new computer (creates both Asset and Computer records).

    Required fields:
    - assetnumber: Business identifier

    Optional fields:
    - name, serialnumber, statusid, locationid, businessunitid
    - computertypeid, hostname, osid
    - isvnc, iswinrm, isshopfloor
    - 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
        )

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

    # Get computer asset type
    computer_type = AssetType.query.filter_by(assettype='computer').first()
    if not computer_type:
        return error_response(
            ErrorCodes.INTERNAL_ERROR,
            'Computer 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=computer_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 computer extension
    comp = Computer(
        assetid=asset.assetid,
        computertypeid=data.get('computertypeid'),
        hostname=data.get('hostname'),
        osid=data.get('osid'),
        loggedinuser=data.get('loggedinuser'),
        lastreporteddate=data.get('lastreporteddate'),
        lastboottime=data.get('lastboottime'),
        isvnc=data.get('isvnc', False),
        iswinrm=data.get('iswinrm', False),
        isshopfloor=data.get('isshopfloor', False)
    )

    db.session.add(comp)
    db.session.flush()

    # Audit log
    AuditLog.log('created', 'Computer', entityid=comp.computerid,
                 entityname=data.get('hostname') or data['assetnumber'])

    db.session.commit()

    result = asset.to_dict()
    result['computer'] = comp.to_dict()

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


@computers_bp.route('/<int:computer_id>', methods=['PUT'])
@jwt_required()
def update_computer(computer_id: int):
    """Update computer (both Asset and Computer records)."""
    comp = Computer.query.get(computer_id)

    if not comp:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Computer with ID {computer_id} not found',
            http_code=404
        )

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

    asset = comp.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
            )

    # Check for conflicting hostname
    if 'hostname' in data and data['hostname'] != comp.hostname:
        existing = Computer.query.filter_by(hostname=data['hostname']).first()
        if existing and existing.computerid != computer_id:
            return error_response(
                ErrorCodes.CONFLICT,
                f"Computer with hostname '{data['hostname']}' already exists",
                http_code=409
            )

    # Track changes for audit log
    changes = {}

    # Update asset fields
    asset_fields = ['assetnumber', 'name', 'serialnumber', 'statusid',
                    'locationid', 'businessunitid', 'mapx', 'mapy', 'notes', 'isactive']
    for key in asset_fields:
        if key in data:
            old_val = getattr(asset, key)
            new_val = data[key]
            if old_val != new_val:
                changes[key] = {'old': old_val, 'new': new_val}
            setattr(asset, key, data[key])

    # Update computer fields
    computer_fields = ['computertypeid', 'hostname', 'osid', 'loggedinuser',
                       'lastreporteddate', 'lastboottime', 'isvnc', 'iswinrm', 'isshopfloor']
    for key in computer_fields:
        if key in data:
            old_val = getattr(comp, key)
            new_val = data[key]
            if old_val != new_val:
                changes[key] = {'old': old_val, 'new': new_val}
            setattr(comp, key, data[key])

    # Audit log if there were changes
    if changes:
        AuditLog.log('updated', 'Computer', entityid=comp.computerid,
                     entityname=comp.hostname or asset.assetnumber, changes=changes)

    db.session.commit()

    result = asset.to_dict()
    result['computer'] = comp.to_dict()

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


@computers_bp.route('/<int:computer_id>', methods=['DELETE'])
@jwt_required()
def delete_computer(computer_id: int):
    """Delete (soft delete) computer."""
    comp = Computer.query.get(computer_id)

    if not comp:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Computer with ID {computer_id} not found',
            http_code=404
        )

    # Soft delete the asset
    comp.asset.isactive = False

    # Audit log
    AuditLog.log('deleted', 'Computer', entityid=comp.computerid,
                 entityname=comp.hostname or comp.asset.assetnumber)

    db.session.commit()

    return success_response(message='Computer deleted')


# =============================================================================
# Installed Applications
# =============================================================================

@computers_bp.route('/<int:computer_id>/apps', methods=['GET'])
@jwt_required(optional=True)
def get_installed_apps(computer_id: int):
    """Get all installed applications for a computer."""
    comp = Computer.query.get(computer_id)

    if not comp:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Computer with ID {computer_id} not found',
            http_code=404
        )

    apps = ComputerInstalledApp.query.filter_by(
        computerid=computer_id,
        isactive=True
    ).all()

    data = [app.to_dict() for app in apps]

    return success_response(data)


@computers_bp.route('/<int:computer_id>/apps', methods=['POST'])
@jwt_required()
def add_installed_app(computer_id: int):
    """Add an installed application to a computer."""
    comp = Computer.query.get(computer_id)

    if not comp:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Computer with ID {computer_id} not found',
            http_code=404
        )

    data = request.get_json()
    if not data or not data.get('appid'):
        return error_response(ErrorCodes.VALIDATION_ERROR, 'appid is required')

    appid = data['appid']

    # Validate app exists
    if not Application.query.get(appid):
        return error_response(ErrorCodes.NOT_FOUND, f'Application {appid} not found', http_code=404)

    # Check for duplicate
    existing = ComputerInstalledApp.query.filter_by(
        computerid=computer_id,
        appid=appid
    ).first()

    if existing:
        if existing.isactive:
            return error_response(
                ErrorCodes.CONFLICT,
                'This application is already installed on this computer',
                http_code=409
            )
        else:
            # Reactivate
            existing.isactive = True
            existing.appversionid = data.get('appversionid')
            db.session.commit()
            return success_response(existing.to_dict(), message='Application reinstalled')

    # Create new installation record
    installed = ComputerInstalledApp(
        computerid=computer_id,
        appid=appid,
        appversionid=data.get('appversionid')
    )

    db.session.add(installed)
    db.session.commit()

    return success_response(installed.to_dict(), message='Application installed', http_code=201)


@computers_bp.route('/<int:computer_id>/apps/<int:app_id>', methods=['DELETE'])
@jwt_required()
def remove_installed_app(computer_id: int, app_id: int):
    """Remove an installed application from a computer."""
    installed = ComputerInstalledApp.query.filter_by(
        computerid=computer_id,
        appid=app_id,
        isactive=True
    ).first()

    if not installed:
        return error_response(
            ErrorCodes.NOT_FOUND,
            'Installation record not found',
            http_code=404
        )

    installed.isactive = False
    db.session.commit()

    return success_response(message='Application uninstalled')


# =============================================================================
# Status Reporting
# =============================================================================

@computers_bp.route('/<int:computer_id>/report', methods=['POST'])
@jwt_required()
def report_status(computer_id: int):
    """
    Report computer status (for agent-based reporting).

    This endpoint can be called periodically by a client agent
    to update status information.
    """
    comp = Computer.query.get(computer_id)

    if not comp:
        return error_response(
            ErrorCodes.NOT_FOUND,
            f'Computer with ID {computer_id} not found',
            http_code=404
        )

    data = request.get_json() or {}

    # Update status fields
    from datetime import datetime
    comp.lastreporteddate = datetime.utcnow()

    if 'loggedinuser' in data:
        comp.loggedinuser = data['loggedinuser']
    if 'lastboottime' in data:
        comp.lastboottime = data['lastboottime']

    db.session.commit()

    return success_response(message='Status reported')


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

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

    # Count by computer type
    by_type = db.session.query(
        ComputerType.computertype,
        db.func.count(Computer.computerid)
    ).join(Computer, Computer.computertypeid == ComputerType.computertypeid
    ).join(Asset, Asset.assetid == Computer.assetid
    ).filter(Asset.isactive == True
    ).group_by(ComputerType.computertype
    ).all()

    # Count by OS
    by_os = db.session.query(
        OperatingSystem.osname,
        db.func.count(Computer.computerid)
    ).join(Computer, Computer.osid == OperatingSystem.osid
    ).join(Asset, Asset.assetid == Computer.assetid
    ).filter(Asset.isactive == True
    ).group_by(OperatingSystem.osname
    ).all()

    # Count shopfloor vs non-shopfloor
    shopfloor_count = db.session.query(Computer).join(Asset).filter(
        Asset.isactive == True,
        Computer.isshopfloor == True
    ).count()

    return success_response({
        'total': total,
        'bytype': [{'type': t, 'count': c} for t, c in by_type],
        'byos': [{'os': o, 'count': c} for o, c in by_os],
        'shopfloor': shopfloor_count,
        'nonshopfloor': total - shopfloor_count
    })
