use std::cmp::Ordering;
use std::fmt;
use std::ops::Range;

use super::ProtocolVersion;
use crate::buffer::Buf;
use crate::util::be_u48;
use nom::bytes::complete::take;
use nom::number::complete::{be_u16, be_u8};
use nom::{Err, IResult};

#[derive(PartialEq, Eq, Default)]
pub struct DTLSRecord {
    pub content_type: ContentType,
    pub version: ProtocolVersion,
    pub sequence: Sequence,
    pub length: u16,
    pub fragment_range: Range<usize>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Sequence {
    pub epoch: u16,
    pub sequence_number: u64, // technically u48
}

impl Sequence {
    pub fn new(epoch: u16) -> Self {
        Self {
            epoch,
            sequence_number: 0,
        }
    }
}

impl DTLSRecord {
    /// DTLS record header length: content_type(1) + version(2) + epoch(2) + seq(6) + length(2)
    pub const HEADER_LEN: usize = 13;

    /// Length of the explicit nonce prefix in AEAD ciphers (e.g., AES-GCM)
    pub const EXPLICIT_NONCE_LEN: usize = 8;

    /// Byte offset in the record header where the 2-byte length field is
    pub const LENGTH_OFFSET: Range<usize> = 11..13;

    pub fn parse(
        input: &[u8],
        base_offset: usize,
        skip_offset: usize,
    ) -> IResult<&[u8], DTLSRecord> {
        let original_input = input;
        let (input, content_type) = ContentType::parse(input)?; // u8
        let (input, version) = ProtocolVersion::parse(input)?; // u16

        // Accept DTLS 1.0 or 1.2 in record layer per RFC 6347
        // DTLS 1.0 (0xFEFF) is often used in record layer during handshake for compatibility
        // The actual protocol version is negotiated in the handshake messages
        match version {
            ProtocolVersion::DTLS1_0 | ProtocolVersion::DTLS1_2 => {
                // Valid DTLS versions for record layer
            }
            _ => {
                return Err(Err::Failure(nom::error::Error::new(
                    input,
                    nom::error::ErrorKind::Tag,
                )));
            }
        }

        let (input, epoch) = be_u16(input)?; // u16
        let (input, sequence_number) = be_u48(input)?; // u48
        let (input, length) = be_u16(input)?; // u16

        // When encrypted, skip_offset is 0 and this has the explicit nonce.
        // When decrypted, skip_offset is > 0 to skip the explicit nonce.
        let input = &input[skip_offset..];

        let (rest, fragment_slice) = take(length as usize)(input)?;

        // Calculate absolute range in root buffer
        // fragment_slice is already offset from original_input by all the header bytes and skip_offset
        let relative_offset = fragment_slice.as_ptr() as usize - original_input.as_ptr() as usize;
        let start = base_offset + relative_offset;
        let end = start + fragment_slice.len();

        let sequence = Sequence {
            epoch,
            sequence_number,
        };

        Ok((
            rest,
            DTLSRecord {
                content_type,
                version,
                sequence,
                length,
                fragment_range: start..end,
            },
        ))
    }

    pub fn fragment<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
        &buf[self.fragment_range.clone()]
    }

    pub fn serialize(&self, buf: &[u8], output: &mut Buf) {
        output.push(self.content_type.as_u8());
        self.version.serialize(output);
        output.extend_from_slice(&self.sequence.epoch.to_be_bytes());
        output.extend_from_slice(&self.sequence.sequence_number.to_be_bytes()[2..]);
        output.extend_from_slice(&self.length.to_be_bytes());
        output.extend_from_slice(self.fragment(buf));
    }

    pub fn nonce<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
        let fragment = self.fragment(buf);
        &fragment[..Self::EXPLICIT_NONCE_LEN]
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentType {
    ChangeCipherSpec,
    Alert,
    Handshake,
    ApplicationData,
    Unknown(u8),
}

impl Default for ContentType {
    fn default() -> Self {
        Self::Unknown(0)
    }
}

impl ContentType {
    pub fn from_u8(value: u8) -> Self {
        match value {
            20 => ContentType::ChangeCipherSpec,
            21 => ContentType::Alert,
            22 => ContentType::Handshake,
            23 => ContentType::ApplicationData,
            _ => ContentType::Unknown(value),
        }
    }

    pub fn as_u8(&self) -> u8 {
        match self {
            ContentType::ChangeCipherSpec => 20,
            ContentType::Alert => 21,
            ContentType::Handshake => 22,
            ContentType::ApplicationData => 23,
            ContentType::Unknown(value) => *value,
        }
    }

    pub fn parse(input: &[u8]) -> IResult<&[u8], ContentType> {
        let (input, byte) = be_u8(input)?;
        Ok((input, Self::from_u8(byte)))
    }
}

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

    const RECORD: &[u8] = &[
        0x16, // ContentType::Handshake
        0xFE, 0xFD, // ProtocolVersion::DTLS1_2
        0x00, 0x01, // epoch
        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // sequence_number
        0x00, 0x10, // length
        // fragment
        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
        0x10,
    ];

    #[test]
    fn roundtrip() {
        // Parse the record with base_offset 0, skip_offset 0
        let (rest, parsed) = DTLSRecord::parse(RECORD, 0, 0).unwrap();
        assert!(rest.is_empty());

        // Serialize and compare to RECORD
        let mut serialized = Buf::new();
        parsed.serialize(RECORD, &mut serialized);
        assert_eq!(&*serialized, RECORD);
    }
}

impl fmt::Display for Sequence {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "[epoch: {}, sequence_number: {}]",
            self.epoch, self.sequence_number,
        )
    }
}

impl Ord for Sequence {
    fn cmp(&self, other: &Self) -> Ordering {
        if self.epoch < other.epoch {
            Ordering::Less
        } else if self.epoch > other.epoch {
            Ordering::Greater
        } else {
            self.sequence_number.cmp(&other.sequence_number)
        }
    }
}

impl PartialOrd for Sequence {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl fmt::Debug for DTLSRecord {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("DTLSRecord")
            .field("content_type", &self.content_type)
            .field("version", &self.version)
            .field("sequence", &self.sequence)
            .field("length", &self.length)
            .field("fragment_range", &self.fragment_range)
            .finish()
    }
}
