use smoltcp::wire::{
    IpAddress, IpEndpoint, Ipv6HopByHopHeader, Ipv6HopByHopRepr, Ipv6OptionFailureType,
    Ipv6OptionRepr, Ipv6Packet,
};
use smoltcp::{
    socket::tcp::Socket as TcpSocket,
    socket::tcp::SocketBuffer as TcpSocketBuffer,
    wire::{IpProtocol, Ipv4Packet, TcpPacket},
};
use tracing::{debug, trace, warn};

use crate::device::Packet;

pub struct ParseResult {
    pub source: IpEndpoint,
    pub dest: IpEndpoint,
    pub socket: TcpSocket<'static>,
}

pub struct Parser {}

impl Parser {
    fn maybe_make_tcp_socket(packet: &TcpPacket<&[u8]>) -> Option<TcpSocket<'static>> {
        if packet.syn() && !packet.ack() {
            Some(TcpSocket::new(
                TcpSocketBuffer::new(vec![0; 4096]),
                TcpSocketBuffer::new(vec![0; 4096]),
            ))
        } else {
            None
        }
    }

    fn handle_tcp_packet(
        src_addr: IpAddress,
        dst_addr: IpAddress,
        payload: &[u8],
    ) -> Option<ParseResult> {
        match TcpPacket::new_checked(payload) {
            Ok(tcp_packet) => {
                #[cfg(feature = "very-verbose")]
                trace!(
                    "pkt {}:{} -> {}:{}{}{}{}{}{}{}",
                    src_addr,
                    tcp_packet.src_port(),
                    dst_addr,
                    tcp_packet.dst_port(),
                    if tcp_packet.syn() { " syn" } else { "" },
                    if tcp_packet.ack() { " ack" } else { "" },
                    if tcp_packet.fin() { " fin" } else { "" },
                    if tcp_packet.rst() { " rst" } else { "" },
                    if tcp_packet.psh() { " psh" } else { "" },
                    if tcp_packet.urg() { " urg" } else { "" },
                );
                if let Some(mut socket) = Self::maybe_make_tcp_socket(&tcp_packet) {
                    let src_port = tcp_packet.src_port();
                    let dst_port = tcp_packet.dst_port();

                    trace!(
                        "New incoming TCP connection: {}:{} -> {}:{}",
                        src_addr,
                        src_port,
                        dst_addr,
                        dst_port
                    );

                    assert!(!socket.is_open());
                    assert!(!socket.is_active());

                    if let Err(e) = socket.listen((dst_addr, dst_port)) {
                        // This is either because it's unaddressable or already open. Either way,
                        // it's not a serious problem.
                        warn!("Failed to create listening socket: {e:?}");
                    }
                    return Some(ParseResult {
                        source: IpEndpoint {
                            addr: src_addr,
                            port: src_port,
                        },
                        dest: IpEndpoint {
                            addr: dst_addr,
                            port: dst_port,
                        },
                        socket,
                    });
                }
            }
            Err(err) => {
                warn!("Unable to create TCP packet: {}", err);
            }
        }
        None
    }

    pub fn parse(packet: &Packet) -> Option<ParseResult> {
        let vers = packet[0] >> 4;
        let (src, dst, protocol, payload) = match vers {
            4 => {
                let pkt = match Ipv4Packet::new_checked(packet.as_slice()) {
                    Ok(v) => v,
                    Err(e) => {
                        debug!("Failed to parse IPv4 packet: {}", e);
                        return None;
                    }
                };
                (
                    IpAddress::Ipv4(pkt.src_addr()),
                    IpAddress::Ipv4(pkt.dst_addr()),
                    pkt.next_header(),
                    pkt.payload(),
                )
            }
            6 => {
                let pkt = match Ipv6Packet::new_checked(packet.as_slice()) {
                    Ok(v) => v,
                    Err(e) => {
                        debug!("Failed to parse IPv6 packet: {}", e);
                        return None;
                    }
                };

                let mut payload = pkt.payload();
                let mut next_header = pkt.next_header();

                // Sigh. IPv6 has a 'nifty' feature that lets the 'next header' actually be this
                // "hop-by-hop protocol" extension that has a bunch of custom option fields.
                // We have to imitate what smoltcp does here and parse it out to see what the
                // *real* protocol and payload are, making sure to drop packets where the
                // options header says we should drop them if we don't understand them.
                //
                // (look at smoltcp src/iface/interface/ipv6.rs for what it does with these)
                if let IpProtocol::HopByHop = next_header {
                    let hbh_header = match Ipv6HopByHopHeader::new_checked(payload) {
                        Ok(v) => v,
                        Err(e) => {
                            debug!("Failed to parse IPv6 hop-by-hop header: {}", e);
                            return None;
                        }
                    };
                    let hbh_repr = match Ipv6HopByHopRepr::parse(&hbh_header) {
                        Ok(v) => v,
                        Err(e) => {
                            debug!("Failed to parse IPv6 hop-by-hop header: {}", e);
                            return None;
                        }
                    };

                    let options = smoltcp::wire::Ipv6OptionsIterator::new(hbh_repr.data);

                    for option in options {
                        match option {
                            Ok(Ipv6OptionRepr::Pad1) | Ok(Ipv6OptionRepr::PadN(_)) => (),

                            Ok(Ipv6OptionRepr::Unknown { type_, .. }) => {
                                match Ipv6OptionFailureType::from(type_) {
                                    Ipv6OptionFailureType::Skip => (),
                                    _ => {
                                        debug!("Discarding IPv6 packet due to unknown hop-by-hop header options: {hbh_repr:?}");
                                        return None;
                                    }
                                }
                            }
                            _ => {
                                debug!("Discarding IPv6 packet due to unparseable hop-by-hop header {hbh_repr:?}");
                                return None;
                            }
                        }
                    }
                    next_header = hbh_repr.next_header;
                    payload = &payload[hbh_repr.header_len()..];
                }

                (
                    IpAddress::Ipv6(pkt.src_addr()),
                    IpAddress::Ipv6(pkt.dst_addr()),
                    next_header,
                    payload,
                )
            }
            x => {
                debug!("Unknown IP protocol version {}", x);
                return None;
            }
        };
        if let IpProtocol::Tcp = protocol {
            Self::handle_tcp_packet(src, dst, payload)
        } else {
            None
        }
    }
}
