"""Global search API endpoint with full search parity."""

import re
import ipaddress
import logging

from datetime import datetime
from flask import Blueprint, request
from flask_jwt_extended import jwt_required
from sqlalchemy.orm import joinedload

from shopdb.extensions import db
from shopdb.core.models import (
    Application, KnowledgeBase,
    Asset, AssetType, Communication, Vendor, Model
)
from shopdb.utils.responses import success_response

logger = logging.getLogger(__name__)

search_bp = Blueprint('search', __name__)

# ServiceNOW URL template
SERVICENOW_URL = (
    'https://geit.service-now.com/now/nav/ui/search/'
    '0f8b85d0c7922010099a308dc7c2606a/params/search-term/{ticket}/'
    'global-search-data-config-id/c861cea2c7022010099a308dc7c26041/'
    'back-button-label/IT4IT%20Homepage/search-context/now%2Fnav%2Fui'
)


def _classify_query(query):
    """Analyze the query string to determine its nature."""
    return {
        'is_ip': bool(re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', query)),
        'is_sso': bool(re.match(r'^\d{9}$', query)),
        'is_servicenow': bool(re.match(r'^(GEINC|GECHG|GERIT|GESCT)\d+', query, re.IGNORECASE)),
        'servicenow_prefix': re.match(r'^(GEINC|GECHG|GERIT|GESCT)', query, re.IGNORECASE).group(1) if re.match(r'^(GEINC|GECHG|GERIT|GESCT)', query, re.IGNORECASE) else None,
        'is_fqdn': bool(re.match(r'^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$', query)),
    }


def _get_asset_result(asset, query, relevance=None):
    """Build a search result dict from an Asset object."""
    asset_type_name = asset.assettype.assettype if asset.assettype else 'asset'

    plugin_id = asset.assetid
    if asset_type_name == 'equipment' and hasattr(asset, 'equipment') and asset.equipment:
        plugin_id = asset.equipment.equipmentid
    elif asset_type_name == 'computer' and hasattr(asset, 'computer') and asset.computer:
        plugin_id = asset.computer.computerid
    elif asset_type_name == 'network_device' and hasattr(asset, 'network_device') and asset.network_device:
        plugin_id = asset.network_device.networkdeviceid
    elif asset_type_name == 'printer' and hasattr(asset, 'printer') and asset.printer:
        plugin_id = asset.printer.printerid

    url_map = {
        'equipment': f"/machines/{plugin_id}",
        'computer': f"/pcs/{plugin_id}",
        'network_device': f"/network/{plugin_id}",
        'printer': f"/printers/{plugin_id}",
    }
    url = url_map.get(asset_type_name, f"/assets/{asset.assetid}")

    display_name = asset.display_name
    subtitle = None
    if asset.name and asset.assetnumber != asset.name:
        subtitle = asset.assetnumber

    location_name = asset.location.locationname if asset.location else None

    if relevance is None:
        relevance = 15

    return {
        'type': asset_type_name,
        'id': plugin_id,
        'title': display_name,
        'subtitle': subtitle,
        'location': location_name,
        'url': url,
        'relevance': relevance
    }


def _search_applications(query, search_term):
    """Search Applications by name and description."""
    results = []
    try:
        apps = Application.query.filter(
            Application.isactive == True,
            db.or_(
                Application.appname.ilike(search_term),
                Application.appdescription.ilike(search_term)
            )
        ).limit(10).all()

        for app in apps:
            relevance = 20
            if query.lower() == app.appname.lower():
                relevance = 100
            elif query.lower() in app.appname.lower():
                relevance = 50

            results.append({
                'type': 'application',
                'id': app.appid,
                'title': app.appname,
                'subtitle': app.appdescription[:100] if app.appdescription else None,
                'url': f"/applications/{app.appid}",
                'relevance': relevance
            })
    except Exception as e:
        logger.error(f"Application search failed: {e}")
    return results


def _search_knowledgebase(query, search_term):
    """Search Knowledge Base by description and keywords."""
    results = []
    try:
        kb_articles = KnowledgeBase.query.filter(
            KnowledgeBase.isactive == True,
            db.or_(
                KnowledgeBase.shortdescription.ilike(search_term),
                KnowledgeBase.keywords.ilike(search_term)
            )
        ).limit(20).all()

        for kb in kb_articles:
            relevance = 10 + (kb.clicks or 0) * 0.1
            if kb.keywords and query.lower() in kb.keywords.lower():
                relevance += 15

            results.append({
                'type': 'knowledgebase',
                'id': kb.linkid,
                'title': kb.shortdescription,
                'subtitle': kb.application.appname if kb.application else None,
                'url': f"/knowledgebase/{kb.linkid}",
                'linkurl': kb.linkurl,
                'relevance': relevance
            })
    except Exception as e:
        logger.error(f"KnowledgeBase search failed: {e}")
    return results


def _search_employees(query, search_term):
    """Search Employees in separate wjf_employees database."""
    results = []
    try:
        import pymysql
        emp_conn = pymysql.connect(
            host='localhost',
            user='root',
            password='rootpassword',
            database='wjf_employees',
            cursorclass=pymysql.cursors.DictCursor
        )

        with emp_conn.cursor() as cur:
            cur.execute('''
                SELECT SSO, First_Name, Last_Name, Team, Role
                FROM employees
                WHERE First_Name LIKE %s
                   OR Last_Name LIKE %s
                   OR CAST(SSO AS CHAR) LIKE %s
                ORDER BY Last_Name, First_Name
                LIMIT 10
            ''', (search_term, search_term, search_term))
            employees = cur.fetchall()

        emp_conn.close()

        for emp in employees:
            full_name = f"{emp['First_Name'].strip()} {emp['Last_Name'].strip()}"
            sso_str = str(emp['SSO'])

            relevance = 20
            if query == sso_str:
                relevance = 100
            elif query.lower() == full_name.lower():
                relevance = 95
            elif query.lower() in full_name.lower():
                relevance = 60

            results.append({
                'type': 'employee',
                'id': emp['SSO'],
                'title': full_name,
                'subtitle': emp.get('Team') or emp.get('Role') or f"SSO: {sso_str}",
                'url': f"/employees/{emp['SSO']}",
                'relevance': relevance
            })
    except Exception as e:
        logger.error(f"Employee search failed: {e}")
    return results


def _search_assets(query, search_term):
    """Search unified Assets table by number, name, serial, notes."""
    results = []
    try:
        assets = Asset.query.join(AssetType).options(
            joinedload(Asset.assettype),
            joinedload(Asset.location),
        ).filter(
            Asset.isactive == True,
            db.or_(
                Asset.assetnumber.ilike(search_term),
                Asset.name.ilike(search_term),
                Asset.serialnumber.ilike(search_term),
                Asset.notes.ilike(search_term)
            )
        ).limit(15).all()

        for asset in assets:
            relevance = 15
            if asset.assetnumber and query.lower() == asset.assetnumber.lower():
                relevance = 100
            elif asset.name and query.lower() == asset.name.lower():
                relevance = 90
            elif asset.serialnumber and query.lower() == asset.serialnumber.lower():
                relevance = 85
            elif asset.name and query.lower() in asset.name.lower():
                relevance = 50

            results.append(_get_asset_result(asset, query, relevance))
    except Exception as e:
        logger.error(f"Asset search failed: {e}")
    return results


def _search_by_ip(query, search_term):
    """Search Communications table for IP address matches."""
    results = []
    try:
        comms = Communication.query.filter(
            Communication.ipaddress.ilike(search_term)
        ).options(
            joinedload(Communication.asset).joinedload(Asset.assettype),
            joinedload(Communication.asset).joinedload(Asset.location),
        ).limit(10).all()

        seen_assets = set()
        for comm in comms:
            asset = comm.asset
            if not asset or not asset.isactive or asset.assetid in seen_assets:
                continue
            seen_assets.add(asset.assetid)

            relevance = 80 if query == comm.ipaddress else 40
            result = _get_asset_result(asset, query, relevance)
            result['subtitle'] = comm.ipaddress
            results.append(result)
    except Exception as e:
        logger.error(f"IP search failed: {e}")
    return results


def _search_subnets(query):
    """Find which subnet an IP address belongs to."""
    results = []
    try:
        from plugins.network.models import Subnet
        ip_obj = ipaddress.ip_address(query)
        subnets = Subnet.query.filter(Subnet.isactive == True).all()
        for subnet in subnets:
            try:
                network = ipaddress.ip_network(subnet.cidr, strict=False)
                if ip_obj in network:
                    results.append({
                        'type': 'subnet',
                        'id': subnet.subnetid,
                        'title': f'{subnet.name} ({subnet.cidr})',
                        'subtitle': subnet.description or subnet.subnettype,
                        'url': f'/network',
                        'relevance': 70
                    })
            except ValueError:
                continue
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Subnet search failed: {e}")
    return results


def _search_hostnames(query, search_term):
    """Search hostname fields across Computer, Printer, NetworkDevice."""
    results = []

    # Search Computers
    try:
        from plugins.computers.models import Computer
        computers = Computer.query.filter(
            Computer.hostname.ilike(search_term)
        ).options(
            joinedload(Computer.asset).joinedload(Asset.assettype),
            joinedload(Computer.asset).joinedload(Asset.location),
        ).limit(10).all()

        for comp in computers:
            if comp.asset and comp.asset.isactive:
                relevance = 85 if query.lower() == (comp.hostname or '').lower() else 40
                result = _get_asset_result(comp.asset, query, relevance)
                result['subtitle'] = comp.hostname
                results.append(result)
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Computer hostname search failed: {e}")

    # Search Printers
    try:
        from plugins.printers.models import Printer
        printers = Printer.query.filter(
            db.or_(
                Printer.hostname.ilike(search_term),
                Printer.sharename.ilike(search_term),
                Printer.windowsname.ilike(search_term),
            )
        ).options(
            joinedload(Printer.asset).joinedload(Asset.assettype),
            joinedload(Printer.asset).joinedload(Asset.location),
        ).limit(10).all()

        for printer in printers:
            if printer.asset and printer.asset.isactive:
                match_field = printer.hostname or printer.sharename or ''
                relevance = 85 if query.lower() == match_field.lower() else 40
                result = _get_asset_result(printer.asset, query, relevance)
                result['subtitle'] = printer.hostname or printer.sharename
                results.append(result)
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Printer hostname search failed: {e}")

    # Search Network Devices
    try:
        from plugins.network.models import NetworkDevice
        devices = NetworkDevice.query.filter(
            NetworkDevice.hostname.ilike(search_term)
        ).options(
            joinedload(NetworkDevice.asset).joinedload(Asset.assettype),
            joinedload(NetworkDevice.asset).joinedload(Asset.location),
        ).limit(10).all()

        for device in devices:
            if device.asset and device.asset.isactive:
                relevance = 85 if query.lower() == (device.hostname or '').lower() else 40
                result = _get_asset_result(device.asset, query, relevance)
                result['subtitle'] = device.hostname
                results.append(result)
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Network device hostname search failed: {e}")

    return results


def _search_notifications(query, search_term):
    """Search notifications with time-weighted relevance."""
    results = []
    try:
        from plugins.notifications.models import Notification

        notifications = Notification.query.options(
            joinedload(Notification.notificationtype)
        ).filter(
            db.or_(
                Notification.notification.ilike(search_term),
                Notification.ticketnumber.ilike(search_term)
            )
        ).order_by(Notification.starttime.desc()).limit(15).all()

        now = datetime.utcnow()
        for notif in notifications:
            base_relevance = 20
            if notif.ticketnumber and query.lower() == notif.ticketnumber.lower():
                base_relevance = 85

            # Time-weighted relevance
            if notif.is_current:
                base_relevance *= 3
            elif notif.starttime and notif.starttime > now:
                base_relevance *= 2
            elif notif.endtime and (now - notif.endtime).days < 7:
                base_relevance = int(base_relevance * 1.5)

            results.append({
                'type': 'notification',
                'id': notif.notificationid,
                'title': notif.title,
                'subtitle': notif.notificationtype.typename if notif.notificationtype else None,
                'url': f'/notifications',
                'relevance': min(int(base_relevance), 100),
                'ticketnumber': notif.ticketnumber,
                'iscurrent': notif.is_current
            })
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Notification search failed: {e}")
    return results


def _search_vendor_model_type(query, search_term):
    """Search assets by vendor name, model name, or equipment/device type name."""
    results = []

    # Equipment: vendor, model, equipmenttype
    try:
        from plugins.equipment.models import Equipment, EquipmentType
        equipment_assets = db.session.query(Asset).join(
            Equipment, Equipment.assetid == Asset.assetid
        ).outerjoin(
            Vendor, Equipment.vendorid == Vendor.vendorid
        ).outerjoin(
            Model, Equipment.modelnumberid == Model.modelnumberid
        ).outerjoin(
            EquipmentType, Equipment.equipmenttypeid == EquipmentType.equipmenttypeid
        ).options(
            joinedload(Asset.assettype),
            joinedload(Asset.location),
        ).filter(
            Asset.isactive == True,
            db.or_(
                Vendor.vendor.ilike(search_term),
                Model.modelnumber.ilike(search_term),
                EquipmentType.equipmenttype.ilike(search_term)
            )
        ).limit(10).all()

        for asset in equipment_assets:
            results.append(_get_asset_result(asset, query, 30))
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Equipment vendor/model/type search failed: {e}")

    # Printers: vendor, model, printertype
    try:
        from plugins.printers.models import Printer, PrinterType
        printer_assets = db.session.query(Asset).join(
            Printer, Printer.assetid == Asset.assetid
        ).outerjoin(
            Vendor, Printer.vendorid == Vendor.vendorid
        ).outerjoin(
            Model, Printer.modelnumberid == Model.modelnumberid
        ).outerjoin(
            PrinterType, Printer.printertypeid == PrinterType.printertypeid
        ).options(
            joinedload(Asset.assettype),
            joinedload(Asset.location),
        ).filter(
            Asset.isactive == True,
            db.or_(
                Vendor.vendor.ilike(search_term),
                Model.modelnumber.ilike(search_term),
                PrinterType.printertype.ilike(search_term)
            )
        ).limit(10).all()

        for asset in printer_assets:
            results.append(_get_asset_result(asset, query, 30))
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Printer vendor/model/type search failed: {e}")

    # Network Devices: vendor, networkdevicetype
    try:
        from plugins.network.models import NetworkDevice, NetworkDeviceType
        netdev_assets = db.session.query(Asset).join(
            NetworkDevice, NetworkDevice.assetid == Asset.assetid
        ).outerjoin(
            Vendor, NetworkDevice.vendorid == Vendor.vendorid
        ).outerjoin(
            NetworkDeviceType, NetworkDevice.networkdevicetypeid == NetworkDeviceType.networkdevicetypeid
        ).options(
            joinedload(Asset.assettype),
            joinedload(Asset.location),
        ).filter(
            Asset.isactive == True,
            db.or_(
                Vendor.vendor.ilike(search_term),
                NetworkDeviceType.networkdevicetype.ilike(search_term)
            )
        ).limit(10).all()

        for asset in netdev_assets:
            results.append(_get_asset_result(asset, query, 30))
    except ImportError:
        pass
    except Exception as e:
        logger.error(f"Network device vendor/type search failed: {e}")

    return results


def _check_smart_redirect(query, classification):
    """Check if query exactly matches a single entity for smart redirect."""
    # Exact SSO match
    if classification['is_sso']:
        try:
            import pymysql
            emp_conn = pymysql.connect(
                host='localhost',
                user='root',
                password='rootpassword',
                database='wjf_employees',
                cursorclass=pymysql.cursors.DictCursor
            )
            with emp_conn.cursor() as cur:
                cur.execute(
                    'SELECT SSO, First_Name, Last_Name FROM employees WHERE SSO = %s LIMIT 1',
                    (query,)
                )
                emp = cur.fetchone()
            emp_conn.close()
            if emp:
                name = f"{emp['First_Name'].strip()} {emp['Last_Name'].strip()}"
                return {
                    'type': 'employee',
                    'url': f"/employees/{emp['SSO']}",
                    'label': name
                }
        except Exception:
            pass

    # Exact asset number match
    try:
        asset = Asset.query.options(
            joinedload(Asset.assettype),
        ).filter(
            Asset.assetnumber == query,
            Asset.isactive == True
        ).first()
        if asset:
            result = _get_asset_result(asset, query)
            return {
                'type': result['type'],
                'url': result['url'],
                'label': asset.display_name
            }
    except Exception:
        pass

    # Exact printer CSF/share name
    try:
        from plugins.printers.models import Printer
        printer = Printer.query.options(
            joinedload(Printer.asset).joinedload(Asset.assettype),
        ).filter(
            db.or_(
                Printer.sharename == query,
                Printer.windowsname == query
            )
        ).first()
        if printer and printer.asset and printer.asset.isactive:
            return {
                'type': 'printer',
                'url': f"/printers/{printer.printerid}",
                'label': printer.sharename or printer.asset.display_name
            }
    except ImportError:
        pass
    except Exception:
        pass

    # Exact hostname match (FQDN or bare hostname)
    hostname_plugins = []
    try:
        from plugins.computers.models import Computer
        hostname_plugins.append(('computer', Computer, 'computerid', '/pcs'))
    except ImportError:
        pass
    try:
        from plugins.printers.models import Printer
        hostname_plugins.append(('printer', Printer, 'printerid', '/printers'))
    except ImportError:
        pass
    try:
        from plugins.network.models import NetworkDevice
        hostname_plugins.append(('network_device', NetworkDevice, 'networkdeviceid', '/network'))
    except ImportError:
        pass

    for type_name, PluginModel, id_field, url_prefix in hostname_plugins:
        try:
            device = PluginModel.query.options(
                joinedload(PluginModel.asset)
            ).filter(
                PluginModel.hostname == query
            ).first()
            if device and device.asset and device.asset.isactive:
                return {
                    'type': type_name,
                    'url': f"{url_prefix}/{getattr(device, id_field)}",
                    'label': device.hostname
                }
        except Exception:
            pass

    # Exact IP match
    if classification['is_ip']:
        try:
            comm = Communication.query.options(
                joinedload(Communication.asset).joinedload(Asset.assettype)
            ).filter(
                Communication.ipaddress == query
            ).first()
            if comm and comm.asset and comm.asset.isactive:
                result = _get_asset_result(comm.asset, query)
                return {
                    'type': result['type'],
                    'url': result['url'],
                    'label': f"{comm.asset.display_name} ({comm.ipaddress})"
                }
        except Exception:
            pass

    return None


@search_bp.route('', methods=['GET'])
@jwt_required(optional=True)
def global_search():
    """
    Global search across multiple entity types.

    Returns combined results from assets, applications, knowledge base,
    employees, notifications, IP addresses, hostnames, and vendor/model/type.
    Supports smart redirects and ServiceNOW ticket detection.
    """
    query = request.args.get('q', '').strip()

    if not query or len(query) < 2:
        return success_response({
            'results': [],
            'query': query,
            'message': 'Search query must be at least 2 characters'
        })

    if len(query) > 200:
        return success_response({
            'results': [],
            'query': query[:200],
            'message': 'Search query too long'
        })

    classification = _classify_query(query)

    # ServiceNOW prefix detection - return redirect immediately
    if classification['is_servicenow']:
        from urllib.parse import quote
        servicenow_url = SERVICENOW_URL.format(ticket=quote(query))
        return success_response({
            'results': [],
            'query': query,
            'total': 0,
            'counts': {},
            'redirect': {
                'type': 'servicenow',
                'url': servicenow_url,
                'label': f'Open {query} in ServiceNOW'
            }
        })

    results = []
    search_term = f'%{query}%'

    # Run all search domains
    results.extend(_search_applications(query, search_term))
    results.extend(_search_knowledgebase(query, search_term))
    results.extend(_search_employees(query, search_term))
    results.extend(_search_assets(query, search_term))
    results.extend(_search_notifications(query, search_term))
    results.extend(_search_hostnames(query, search_term))
    results.extend(_search_vendor_model_type(query, search_term))

    # IP-specific searches
    if classification['is_ip']:
        results.extend(_search_by_ip(query, search_term))
        results.extend(_search_subnets(query))

    # Sort by relevance (highest first)
    results.sort(key=lambda x: x['relevance'], reverse=True)

    # Remove duplicates (prefer higher relevance)
    seen_ids = {}
    unique_results = []
    for r in results:
        key = (r['type'], r['id'])
        if key not in seen_ids:
            seen_ids[key] = True
            unique_results.append(r)

    # Compute type counts before truncation
    type_counts = {}
    for r in unique_results:
        t = r['type']
        type_counts[t] = type_counts.get(t, 0) + 1

    total_all = len(unique_results)

    # Limit total results
    unique_results = unique_results[:50]

    # Check for smart redirect
    response_data = {
        'results': unique_results,
        'query': query,
        'total': len(unique_results),
        'total_all': total_all,
        'counts': type_counts,
    }

    redirect = _check_smart_redirect(query, classification)
    if redirect:
        response_data['redirect'] = redirect

    return success_response(response_data)
