//! TCP (Transmission Control Protocol) parsing

use crate::network::dpi;
use crate::network::parser::{ParsedPacket, ParserConfig};
use crate::network::protocol::{TransportParams, orient_endpoints};
use crate::network::types::{Protocol, ProtocolState, TcpState};

// Define TCP flags as bit masks
const TCP_FIN: u8 = 0x01;
const TCP_SYN: u8 = 0x02;
const TCP_RST: u8 = 0x04;
const TCP_ACK: u8 = 0x10;

/// TCP flags from the TCP header
#[derive(Debug, Clone, Copy)]
pub struct TcpFlags {
    pub fin: bool,
    pub syn: bool,
    pub rst: bool,
    pub ack: bool,
}

/// TCP header information extracted from the packet
#[derive(Debug, Clone, Copy)]
pub struct TcpHeaderInfo {
    pub seq: u32,         // Sequence number
    pub ack: u32,         // Acknowledgment number
    pub window: u16,      // Window size
    pub flags: TcpFlags,  // TCP flags
    pub payload_len: u32, // Actual TCP payload length (not including headers)
}

/// Parse TCP flags from the flags byte
pub(crate) fn parse_tcp_flags(flags: u8) -> TcpFlags {
    TcpFlags {
        fin: (flags & TCP_FIN) != 0,
        syn: (flags & TCP_SYN) != 0,
        rst: (flags & TCP_RST) != 0,
        ack: (flags & TCP_ACK) != 0,
    }
}

/// Parse a TCP packet
pub(crate) fn parse(
    transport_data: &[u8],
    params: TransportParams,
    config: &ParserConfig,
    local_ips: &std::collections::HashSet<std::net::IpAddr>,
) -> Option<ParsedPacket> {
    if transport_data.len() < 20 {
        return None;
    }

    let src_port = u16::from_be_bytes([transport_data[0], transport_data[1]]);
    let dst_port = u16::from_be_bytes([transport_data[2], transport_data[3]]);

    // Extract TCP header fields
    let seq = u32::from_be_bytes([
        transport_data[4],
        transport_data[5],
        transport_data[6],
        transport_data[7],
    ]);
    let ack = u32::from_be_bytes([
        transport_data[8],
        transport_data[9],
        transport_data[10],
        transport_data[11],
    ]);
    let window = u16::from_be_bytes([transport_data[14], transport_data[15]]);
    let flags = transport_data[13];

    let tcp_flags = parse_tcp_flags(flags);

    // Calculate actual TCP payload length. A data offset below 5 is invalid
    // (RFC 9293 §3.1) — treating it as a header length would make the
    // payload overlap the header itself.
    let tcp_header_len = ((transport_data[12] >> 4) as usize) * 4;
    if tcp_header_len < 20 {
        return None;
    }
    let tcp_payload_len = transport_data.len().saturating_sub(tcp_header_len) as u32;

    let tcp_header = TcpHeaderInfo {
        seq,
        ack,
        window,
        flags: tcp_flags,
        payload_len: tcp_payload_len,
    };

    // Log TCP flags for debugging
    log::trace!(
        "TCP flags: FIN={} SYN={} RST={} ACK={}",
        tcp_flags.fin,
        tcp_flags.syn,
        tcp_flags.rst,
        tcp_flags.ack
    );

    let (local_addr, remote_addr, is_outgoing) =
        orient_endpoints(&params, src_port, dst_port, local_ips);

    // Perform DPI if enabled and there's payload
    let dpi_result = if config.enable_dpi {
        if transport_data.len() > tcp_header_len {
            let payload = &transport_data[tcp_header_len..];
            dpi::analyze_tcp_packet(payload, local_addr.port(), remote_addr.port(), is_outgoing)
        } else {
            None
        }
    } else {
        None
    };

    let mut packet = ParsedPacket::new(
        Protocol::Tcp,
        local_addr,
        remote_addr,
        ProtocolState::Tcp(TcpState::Unknown),
        is_outgoing,
        params.packet_len,
        params.process_name,
        params.process_id,
    );
    packet.tcp_header = Some(tcp_header);
    packet.dpi_result = dpi_result;
    Some(packet)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tcp_flags_parsing() {
        let flags = parse_tcp_flags(0x02); // SYN
        assert!(flags.syn);
        assert!(!flags.ack);
        assert!(!flags.fin);

        let flags = parse_tcp_flags(0x12); // SYN + ACK
        assert!(flags.syn);
        assert!(flags.ack);

        let flags = parse_tcp_flags(0x11); // FIN + ACK
        assert!(flags.fin);
        assert!(flags.ack);
    }
}
