"""
Helper functions for ARP operations, including enabling and disabling ARP spoofing.
"""

import logging
from time import sleep
import traceback
from typing import Any

from scapy.layers.l2 import ARP, Ether
from scapy.sendrecv import sendp

from model.database_manager import DatabaseClient
from shared.networking_helpers import get_default_route

logger = logging.getLogger(__name__)


def disable_arp_spoofing_for_device(
    db_client: DatabaseClient,
    device_id: int,
) -> dict[str, Any]:
    """
    Disables ARP spoofing for a single device by sending correct ARP mappings
    to restore the victim's and gateway's ARP tables, then updates the database.

    Args:
        db_client: Database client for updating device state
        device_id: Database ID of the device to disable spoofing for

    Returns:
        Dictionary with result information from database update
    """

    # First get the device information
    device_req = db_client.send_request("get_device_details", {"device_id": device_id})
    device_resp = db_client.wait_for_response(device_req)

    if not device_resp.success:
        logger.error(f"Error getting device {device_id}: {device_resp.error}")
        return {
            "success": False,
            "error": f"Failed to get device info: {device_resp.error}",
        }

    device = device_resp.data
    if not device or not device.get("mac_addr") or not device.get("ip_addr"):
        logger.error(f"Device {device_id} missing required MAC or IP address")
        return {"success": False, "error": "Device missing required MAC or IP address"}

    # Send corrective ARP packets and update database
    return _disable_arp_spoofing_by_addresses(
        db_client, device_id, device["mac_addr"], device["ip_addr"]
    )


def _disable_arp_spoofing_by_addresses(
    db_client: DatabaseClient,
    device_id: int,
    victim_mac_addr: str,
    victim_ip_addr: str,
) -> dict[str, Any]:
    """
    Internal helper that disables ARP spoofing using provided MAC and IP addresses.
    Sends correct ARP mappings and updates the database.

    Args:
        db_client: Database client for updating device state
        device_id: Database ID of the device to disable spoofing for
        victim_mac_addr: MAC address of the victim device
        victim_ip_addr: IP address of the victim device

    Returns:
        Dictionary with result information from database update
    """

    # Get network information
    route = get_default_route()
    gateway_ip_addr = route.gateway_ip
    gateway_mac_addr = route.gateway_mac
    host_active_interface = route.interface

    if gateway_mac_addr is None:
        logger.warning(
            f"Gateway (ip: {gateway_ip_addr}) MAC address not found. Cannot restore ARP tables."
        )
        # Still update database even if we can't send packets
        return _update_device_arp_status(db_client, device_id, False)

    if victim_ip_addr == gateway_ip_addr:
        logger.debug("Device is the gateway, skipping ARP restoration")
        return _update_device_arp_status(db_client, device_id, False)

    logger.info(
        f"Disabling ARP spoofing for device {victim_ip_addr} ({victim_mac_addr})"
    )

    try:
        for _ in range(0, 5):
            # Send correct ARP mapping to gateway: tell gateway the victim's real MAC address
            gateway_arp = ARP()
            gateway_arp.op = 2  # ARP reply
            gateway_arp.psrc = victim_ip_addr
            gateway_arp.hwsrc = victim_mac_addr  # Victim's real MAC, not host MAC
            gateway_arp.pdst = gateway_ip_addr
            gateway_arp.hwdst = gateway_mac_addr

            sendp(Ether() / gateway_arp, iface=host_active_interface, verbose=0)
            logger.info(
                f"Sent correct ARP mapping to gateway: {victim_ip_addr} -> {victim_mac_addr}"
            )

            # Send correct ARP mapping to victim: tell victim the gateway's real MAC address
            victim_arp = ARP()
            victim_arp.op = 2  # ARP reply
            victim_arp.psrc = gateway_ip_addr
            victim_arp.hwsrc = gateway_mac_addr  # Gateway's real MAC, not host MAC
            victim_arp.pdst = victim_ip_addr
            victim_arp.hwdst = victim_mac_addr

            sendp(Ether() / victim_arp, iface=host_active_interface, verbose=0)
            logger.info(
                f"Sent correct ARP mapping to victim: {gateway_ip_addr} -> {gateway_mac_addr}"
            )
            sleep(1)

    except Exception as e:
        logger.error(
            f"Error sending corrective ARP packets for device {victim_ip_addr}: {e}"
        )
        logger.debug(traceback.format_exc())

    # Always update database regardless of packet sending success/failure
    return _update_device_arp_status(db_client, device_id, False)


def disable_arp_spoofing_for_all_devices(db_client: DatabaseClient) -> dict[str, Any]:
    """
    Disables ARP spoofing for all currently spoofed devices by sending correct
    ARP mappings and updating the database. Uses a single database query for efficiency.

    Args:
        db_client: Database client for querying and updating device state

    Returns:
        Dictionary with success status and details about processed devices
    """

    # Get all currently ARP spoofed devices with their network info
    devices_req = db_client.send_request("get_devices")
    devices_resp = db_client.wait_for_response(devices_req)

    if not devices_resp.success:
        logger.error(f"Error getting devices: {devices_resp.error}")
        return {
            "success": False,
            "error": f"Failed to get devices: {devices_resp.error}",
        }

    if not devices_resp.data:
        logger.info("No devices found")
        return {"success": True, "processed": 0, "errors": []}

    # Filter for ARP spoofed devices with valid network info
    spoofed_devices = []
    for device in devices_resp.data:
        if (
            device.get("is_arp_spoofed")
            and device.get("mac_addr")
            and device.get("ip_addr")
        ):
            spoofed_devices.append(device)

    if not spoofed_devices:
        logger.info("No ARP spoofed devices found")
        return {"success": True, "processed": 0, "errors": []}

    logger.info(f"Disabling ARP spoofing for {len(spoofed_devices)} devices")

    processed_count = 0
    errors = []

    # Process each device
    for device in spoofed_devices:
        try:
            result = _disable_arp_spoofing_by_addresses(
                db_client, device["id"], device["mac_addr"], device["ip_addr"]
            )

            if result.get("success"):
                processed_count += 1
            else:
                errors.append(
                    f"Device {device['id']}: {result.get('error', 'Unknown error')}"
                )

        except Exception as e:
            error_msg = f"Device {device['id']}: {str(e)}"
            logger.error(error_msg)
            errors.append(error_msg)

    logger.info(f"Successfully disabled ARP spoofing for {processed_count} devices")
    if errors:
        logger.warning(f"Encountered {len(errors)} errors during bulk disable")

    return {
        "success": True,
        "processed": processed_count,
        "total": len(spoofed_devices),
        "errors": errors,
    }


def enable_arp_spoofing_for_device(
    db_client: DatabaseClient,
    device_id: int,
) -> dict[str, Any]:
    """
    Enables ARP spoofing for a single device by updating the database.

    Args:
        db_client: Database client for updating device state
        device_id: Database ID of the device to enable spoofing for

    Returns:
        Dictionary with result information from database update
    """
    return _update_device_arp_status(db_client, device_id, True)


def _update_device_arp_status(
    db_client: DatabaseClient,
    device_id: int,
    desired_state: bool,
) -> dict[str, Any]:
    """
    Internal helper to update device ARP spoofing status in database.

    Args:
        db_client: Database client for updating device state
        device_id: Database ID of the device
        desired_state: True to enable, False to disable ARP spoofing

    Returns:
        Dictionary with result information from database update
    """
    update_req = db_client.send_request(
        "toggle_device_arp_spoof",
        {"device_id": device_id, "desired_state": desired_state},
    )
    update_resp = db_client.wait_for_response(update_req)

    if not update_resp.success:
        logger.error(
            f"Failed to update database for device {device_id}: {update_resp.error}"
        )
        return {"success": False, "error": update_resp.error}
    else:
        action = "enabled" if desired_state else "disabled"
        logger.info(f"Successfully {action} ARP spoofing for device {device_id}")
        return {"success": True, "data": update_resp.data}
