use std::collections::VecDeque;
use std::net::{ToSocketAddrs, UdpSocket};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::Instant;

use crate::config::SenderConfig;
use crate::raw_socket::RawSender;

/// Per-thread packet queue capacity.
const QUEUE_LEN: usize = 200;

/// Max pixels per packet. Constrained by MTU 1500:
/// 1500 (IP payload max) - 20 (IP hdr) - 8 (UDP hdr) - 2 (pixel hdr) = 1470
/// 1470 / 7 bytes per pixel = 210 pixels.
const PIXELS_PER_PACKET: usize = 210;
const MSGSIZE: usize = 2 + 7 * PIXELS_PER_PACKET;

/// Maximum number of packets to batch into a single sendmmsg call.
const SEND_BATCH_SIZE: usize = 26;

// ---------------------------------------------------------------------------
// Packet + per-thread queue
// ---------------------------------------------------------------------------

/// A filled buffer ready to be sent, carrying its valid data length.
struct Packet {
    buf: Box<[u8; MSGSIZE]>,
    len: usize,
}

/// Per-thread queue shared between a packer and its sender.
struct SenderQueue {
    pending: Mutex<VecDeque<Packet>>,
    condvar: Condvar,
    pool: Mutex<Vec<Box<[u8; MSGSIZE]>>>,
}

/// Allocates a fresh buffer with the protocol header bytes set.
fn alloc_buf() -> Box<[u8; MSGSIZE]> {
    let mut buf = Box::new([0u8; MSGSIZE]);
    buf[0] = 0x00;
    buf[1] = 0x01;
    buf
}

/// Grabs an empty buffer: try pool first, steal oldest pending, or allocate.
fn grab_buf(queue: &SenderQueue) -> Box<[u8; MSGSIZE]> {
    if let Some(buf) = queue.pool.lock().unwrap().pop() {
        return buf;
    }
    if let Some(oldest) = queue.pending.lock().unwrap().pop_front() {
        return oldest.buf;
    }
    alloc_buf()
}

/// Submits a filled packet to the sender queue and wakes the sender.
fn submit_packet(queue: &SenderQueue, buf: Box<[u8; MSGSIZE]>, pixel_count: usize) {
    let len = 2 + pixel_count * 7;
    queue.pending.lock().unwrap().push_back(Packet { buf, len });
    queue.condvar.notify_one();
}

// ---------------------------------------------------------------------------
// Simple PRNG for deterministic permutation generation
// ---------------------------------------------------------------------------

struct Xorshift64(u64);

impl Xorshift64 {
    fn next(&mut self) -> u64 {
        let mut x = self.0;
        x ^= x << 13;
        x ^= x >> 7;
        x ^= x << 17;
        self.0 = x;
        x
    }
}

fn fisher_yates_shuffle(arr: &mut [usize], rng: &mut Xorshift64) {
    for i in (1..arr.len()).rev() {
        let j = (rng.next() as usize) % (i + 1);
        arr.swap(i, j);
    }
}

// ---------------------------------------------------------------------------
// Display
// ---------------------------------------------------------------------------

/// Collects pixels into a frame buffer and publishes completed frames for
/// packer threads to consume.
///
/// Architecture:
///   main thread -> write_raw_pixels() -> flush_frame()
///                                            |
///                      publishes Arc<Vec<[u8;7]>> frame snapshot
///                                            |
///                  N packer threads (each with unique noise permutation)
///                  continuously iterate the frame, packing pixels into packets
///                                            |
///                  N sender threads (one per packer, own packet queue)
///                  send via RawSender (AF_PACKET) or fallback UDP
///
/// Multiple sender configs are supported -- each config spawns its own group
/// of packer/sender thread pairs targeting a different destination.
pub struct Display {
    /// Frame being assembled by the main thread.
    building_frame: Vec<[u8; 7]>,
    /// Shared pointer to the latest completed frame.
    /// Packer threads clone the Arc to read it without holding the lock.
    current_frame: Arc<Mutex<Arc<Vec<[u8; 7]>>>>,
}

impl Display {
    /// Creates a new Display and spawns packer + sender thread pairs for
    /// each sender in the config.
    pub fn new(senders: &[SenderConfig]) -> Self {
        let current_frame = Arc::new(Mutex::new(Arc::new(Vec::new())));

        let mut global_thread_idx: usize = 0;

        for (sender_idx, sender) in senders.iter().enumerate() {
            let host = &sender.address;
            let port = sender.port;
            let interface = sender.interface.as_deref();
            let num_threads = sender.threads;

            eprintln!(
                "[display] Sender {}: {}:{}, interface={}, threads={}",
                sender_idx,
                host,
                port,
                interface.unwrap_or("auto"),
                num_threads,
            );

            for t in 0..num_threads {
                // Each packer-sender pair gets its own queue.
                let queue = Arc::new(SenderQueue {
                    pending: Mutex::new(VecDeque::with_capacity(QUEUE_LEN)),
                    condvar: Condvar::new(),
                    pool: Mutex::new({
                        let mut v = Vec::with_capacity(QUEUE_LEN);
                        for _ in 0..QUEUE_LEN {
                            v.push(alloc_buf());
                        }
                        v
                    }),
                });

                // Spawn packer thread.
                let frame_source = Arc::clone(&current_frame);
                let packer_queue = Arc::clone(&queue);
                // Each thread gets a globally unique seed.
                let seed = (global_thread_idx as u64 + 1).wrapping_mul(0x517cc1b727220a95);

                let packer_name = format!("packer-{}-{}", sender_idx, t);
                thread::Builder::new()
                    .name(packer_name)
                    .spawn(move || {
                        packer_loop(frame_source, packer_queue, seed);
                    })
                    .unwrap();

                // Spawn sender thread.
                let sender_queue = Arc::clone(&queue);
                let sender_name = format!("sender-{}-{}", sender_idx, t);
                match RawSender::new(host, port, 0, interface) {
                    Ok(raw_sender) => {
                        eprintln!("[display]   {}: raw AF_PACKET sender", sender_name);
                        thread::Builder::new()
                            .name(sender_name)
                            .spawn(move || {
                                sender_loop_raw(raw_sender, sender_queue);
                            })
                            .unwrap();
                    }
                    Err(e) => {
                        eprintln!(
                            "[display]   {}: raw unavailable ({}), falling back to UDP",
                            sender_name, e
                        );
                        let remote_addr = (host.as_str(), port)
                            .to_socket_addrs()
                            .expect("Invalid remote address")
                            .next()
                            .expect("Could not resolve host");
                        let socket = UdpSocket::bind("0.0.0.0:0").expect("Could not bind");
                        socket.connect(remote_addr).expect("Could not connect");
                        thread::Builder::new()
                            .name(sender_name)
                            .spawn(move || {
                                sender_loop_udp(socket, sender_queue);
                            })
                            .unwrap();
                    }
                }

                global_thread_idx += 1;
            }
        }

        Display {
            building_frame: Vec::with_capacity(60_000),
            current_frame,
        }
    }

    /// Appends raw 7-byte pixel entries to the frame being built.
    #[inline]
    pub fn write_raw_pixels(&mut self, pixels: &[[u8; 7]]) {
        self.building_frame.extend_from_slice(pixels);
    }

    /// Publishes the current frame for packer threads and starts a new one.
    pub fn flush_frame(&mut self) {
        let frame = std::mem::take(&mut self.building_frame);
        let capacity = frame.len();
        let new_frame = Arc::new(frame);
        *self.current_frame.lock().unwrap() = new_frame;
        // Pre-allocate next frame to the same size.
        self.building_frame.reserve(capacity);
    }
}

// ---------------------------------------------------------------------------
// Packer thread
// ---------------------------------------------------------------------------

/// Continuously reads the current frame and packs pixels in a noise-shuffled
/// order into packets, feeding them to the paired sender thread.
///
/// Each packer has a unique fixed permutation so all 5 threads naturally
/// cover different regions of the image at any given moment.
fn packer_loop(
    frame_source: Arc<Mutex<Arc<Vec<[u8; 7]>>>>,
    queue: Arc<SenderQueue>,
    seed: u64,
) {
    let mut rng = Xorshift64(seed);
    let mut permutation: Vec<usize> = Vec::new();
    let mut last_frame_len: usize = 0;

    loop {
        // Snapshot the current frame (just an Arc clone -- very cheap).
        let frame = frame_source.lock().unwrap().clone();

        if frame.is_empty() {
            thread::yield_now();
            continue;
        }

        // Rebuild index array only when frame size changes; otherwise
        // re-shuffle the existing permutation in place.
        if frame.len() != last_frame_len {
            permutation = (0..frame.len()).collect();
            last_frame_len = frame.len();
        }
        fisher_yates_shuffle(&mut permutation, &mut rng);

        // Walk the permutation, packing pixels into packets.
        let mut buf = grab_buf(&queue);
        let mut pos: usize = 0;

        for &idx in &permutation {
            if idx >= frame.len() {
                continue;
            }

            let dst_offset = 2 + pos * 7;
            buf[dst_offset..dst_offset + 7].copy_from_slice(&frame[idx]);
            pos += 1;

            if pos == PIXELS_PER_PACKET {
                submit_packet(&queue, buf, pos);
                buf = grab_buf(&queue);
                pos = 0;
            }
        }

        // Flush any remaining partial packet.
        if pos > 0 {
            submit_packet(&queue, buf, pos);
        }
    }
}

// ---------------------------------------------------------------------------
// Sender threads
// ---------------------------------------------------------------------------

/// Raw AF_PACKET sender loop.
fn sender_loop_raw(raw_sender: RawSender, queue: Arc<SenderQueue>) {
    let mut batch: Vec<Packet> = Vec::with_capacity(SEND_BATCH_SIZE);
    let mut frame_buf: Vec<u8> = Vec::with_capacity(SEND_BATCH_SIZE * (MSGSIZE + 42));

    let mut stats_sent: u64 = 0;
    let mut stats_bytes: u64 = 0;
    let mut stats_errors: u64 = 0;
    let mut stats_send_us: u128 = 0;
    let mut stats_send_calls: u64 = 0;
    let mut stats_last_report = Instant::now();

    loop {
        {
            let mut pending = queue.pending.lock().unwrap();
            while pending.is_empty() {
                pending = queue.condvar.wait(pending).unwrap();
            }
            let n = pending.len().min(SEND_BATCH_SIZE);
            batch.extend(pending.drain(..n));
        }

        let payload_ptrs: Vec<&[u8]> = batch.iter().map(|p| &p.buf[..p.len]).collect();

        let send_start = Instant::now();
        match raw_sender.send_batch(&payload_ptrs, &mut frame_buf) {
            Ok(n) => {
                stats_sent += n as u64;
                for packet in batch.iter().take(n) {
                    stats_bytes += (packet.len + raw_sender.header_size()) as u64;
                }
            }
            Err(e) => {
                if stats_errors == 0 {
                    eprintln!("sendmmsg (raw) failed: {}", e);
                }
                stats_errors += batch.len() as u64;
            }
        }
        stats_send_us += send_start.elapsed().as_micros();
        stats_send_calls += 1;

        // Return buffers to the pool.
        {
            let mut pool = queue.pool.lock().unwrap();
            for packet in batch.drain(..) {
                pool.push(packet.buf);
            }
        }

        let now = Instant::now();
        let elapsed = (now - stats_last_report).as_secs_f64();
        if elapsed >= 5.0 {
            let pps = stats_sent as f64 / elapsed;
            let mbps = stats_bytes as f64 * 8.0 / elapsed / 1_000_000.0;
            eprintln!(
                "[sender]    {:.0} pkt/s, {:.1} Mbit/s sent | errors: {} | send: {:.0}us avg over {} calls",
                pps, mbps, stats_errors,
                stats_send_us as f64 / stats_send_calls.max(1) as f64,
                stats_send_calls,
            );
            stats_sent = 0;
            stats_bytes = 0;
            stats_errors = 0;
            stats_send_us = 0;
            stats_send_calls = 0;
            stats_last_report = now;
        }
    }
}

/// Fallback UDP sender loop.
fn sender_loop_udp(socket: UdpSocket, queue: Arc<SenderQueue>) {
    let mut batch: Vec<Packet> = Vec::with_capacity(SEND_BATCH_SIZE);
    let mut stats_sent: u64 = 0;
    let mut stats_bytes: u64 = 0;
    let mut stats_last_report = Instant::now();

    loop {
        {
            let mut pending = queue.pending.lock().unwrap();
            while pending.is_empty() {
                pending = queue.condvar.wait(pending).unwrap();
            }
            let n = pending.len().min(SEND_BATCH_SIZE);
            batch.extend(pending.drain(..n));
        }

        for packet in &batch {
            if let Ok(n) = socket.send(&packet.buf[..packet.len]) {
                stats_sent += 1;
                stats_bytes += n as u64;
            }
        }

        {
            let mut pool = queue.pool.lock().unwrap();
            for packet in batch.drain(..) {
                pool.push(packet.buf);
            }
        }

        let now = Instant::now();
        let elapsed = (now - stats_last_report).as_secs_f64();
        if elapsed >= 5.0 {
            let pps = stats_sent as f64 / elapsed;
            let mbps = stats_bytes as f64 * 8.0 / elapsed / 1_000_000.0;
            eprintln!("[sender]    {:.0} pkt/s, {:.1} Mbit/s (UDP fallback)", pps, mbps);
            stats_sent = 0;
            stats_bytes = 0;
            stats_last_report = now;
        }
    }
}
