use std::{
    collections::HashMap,
    fmt::Debug,
    ops::{Deref, DerefMut},
    sync::Arc,
};

use bytes::{Buf, BufMut, BytesMut};

use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};

use super::{Frame, StreamUni, VarInt, VarIntUnexpectedEnd};

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Setting(pub VarInt);

impl Setting {
    pub fn decode<B: Buf>(buf: &mut B) -> Result<Self, VarIntUnexpectedEnd> {
        Ok(Setting(VarInt::decode(buf)?))
    }

    pub fn encode<B: BufMut>(&self, buf: &mut B) {
        self.0.encode(buf)
    }

    // Reference : https://datatracker.ietf.org/doc/html/rfc9114#section-7.2.4.1
    pub fn is_grease(&self) -> bool {
        let val = self.0.into_inner();
        if val < 0x21 {
            return false;
        }

        #[allow(unknown_lints, clippy::manual_is_multiple_of)]
        {
            (val - 0x21) % 0x1f == 0
        }
    }
}

impl Debug for Setting {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Setting::QPACK_MAX_TABLE_CAPACITY => write!(f, "QPACK_MAX_TABLE_CAPACITY"),
            Setting::MAX_FIELD_SECTION_SIZE => write!(f, "MAX_FIELD_SECTION_SIZE"),
            Setting::QPACK_BLOCKED_STREAMS => write!(f, "QPACK_BLOCKED_STREAMS"),
            Setting::ENABLE_CONNECT_PROTOCOL => write!(f, "ENABLE_CONNECT_PROTOCOL"),
            Setting::ENABLE_DATAGRAM => write!(f, "ENABLE_DATAGRAM"),
            Setting::ENABLE_DATAGRAM_DEPRECATED => write!(f, "ENABLE_DATAGRAM_DEPRECATED"),
            Setting::WEBTRANSPORT_ENABLE_DEPRECATED => write!(f, "WEBTRANSPORT_ENABLE_DEPRECATED"),
            Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED => {
                write!(f, "WEBTRANSPORT_MAX_SESSIONS_DEPRECATED")
            }
            Setting::WEBTRANSPORT_MAX_SESSIONS => write!(f, "WEBTRANSPORT_MAX_SESSIONS"),
            x if x.is_grease() => write!(f, "GREASE SETTING [{:x?}]", x.0.into_inner()),
            x => write!(f, "UNKNOWN_SETTING [{:x?}]", x.0.into_inner()),
        }
    }
}

macro_rules! settings {
    {$($name:ident = $val:expr,)*} => {
        impl Setting {
            $(pub const $name: Setting = Setting(VarInt::from_u32($val));)*
        }
    }
}

settings! {
    // These are for HTTP/3 and we can ignore them
    QPACK_MAX_TABLE_CAPACITY = 0x1, // default is 0, which disables QPACK dynamic table
    MAX_FIELD_SECTION_SIZE = 0x6,
    QPACK_BLOCKED_STREAMS = 0x7,

    // Both of these are required for WebTransport
    ENABLE_CONNECT_PROTOCOL = 0x8,
    ENABLE_DATAGRAM = 0x33,
    ENABLE_DATAGRAM_DEPRECATED = 0xFFD277, // still used by Chrome

    // Removed in draft 06
    WEBTRANSPORT_ENABLE_DEPRECATED = 0x2b603742,
    WEBTRANSPORT_MAX_SESSIONS_DEPRECATED = 0x2b603743,

    // New way to enable WebTransport
    WEBTRANSPORT_MAX_SESSIONS = 0xc671706a,
}

#[derive(Error, Debug, Clone)]
pub enum SettingsError {
    #[error("unexpected end of input")]
    UnexpectedEnd,

    #[error("unexpected stream type {0:?}")]
    UnexpectedStreamType(StreamUni),

    #[error("unexpected frame {0:?}")]
    UnexpectedFrame(Frame),

    #[error("invalid size")]
    InvalidSize,

    #[error("io error: {0}")]
    Io(Arc<std::io::Error>),
}

impl From<std::io::Error> for SettingsError {
    fn from(err: std::io::Error) -> Self {
        SettingsError::Io(Arc::new(err))
    }
}

// A map of settings to values.
#[derive(Default, Debug)]
pub struct Settings(HashMap<Setting, VarInt>);

impl Settings {
    pub fn decode<B: Buf>(buf: &mut B) -> Result<Self, SettingsError> {
        let typ = StreamUni::decode(buf).map_err(|_| SettingsError::UnexpectedEnd)?;
        if typ != StreamUni::CONTROL {
            return Err(SettingsError::UnexpectedStreamType(typ));
        }

        let (typ, mut data) = Frame::read(buf).map_err(|_| SettingsError::UnexpectedEnd)?;
        if typ != Frame::SETTINGS {
            return Err(SettingsError::UnexpectedFrame(typ));
        }

        let mut settings = Settings::default();
        while data.has_remaining() {
            // These return a different error because retrying won't help.
            let id = Setting::decode(&mut data).map_err(|_| SettingsError::InvalidSize)?;
            let value = VarInt::decode(&mut data).map_err(|_| SettingsError::InvalidSize)?;
            // Only add if it is not grease
            if !id.is_grease() {
                settings.0.insert(id, value);
            }
        }

        Ok(settings)
    }

    pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, SettingsError> {
        let mut buf = Vec::new();

        loop {
            if stream.read_buf(&mut buf).await? == 0 {
                return Err(SettingsError::UnexpectedEnd);
            }

            // Look at the buffer we've already read.
            let mut limit = std::io::Cursor::new(&buf);

            match Settings::decode(&mut limit) {
                Ok(settings) => return Ok(settings),
                Err(SettingsError::UnexpectedEnd) => continue, // More data needed.
                Err(e) => return Err(e),
            };
        }
    }

    pub fn encode<B: BufMut>(&self, buf: &mut B) {
        StreamUni::CONTROL.encode(buf);
        Frame::SETTINGS.encode(buf);

        // Encode to a temporary buffer so we can learn the length.
        // TODO avoid doing this, just use a fixed size varint.
        let mut tmp = Vec::new();
        for (id, value) in &self.0 {
            id.encode(&mut tmp);
            value.encode(&mut tmp);
        }

        VarInt::from_u32(tmp.len() as u32).encode(buf);
        buf.put_slice(&tmp);
    }

    pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), SettingsError> {
        // TODO avoid allocating to the heap
        let mut buf = BytesMut::new();
        self.encode(&mut buf);
        stream.write_all_buf(&mut buf).await?;
        Ok(())
    }

    pub fn enable_webtransport(&mut self, max_sessions: u32) {
        let max = VarInt::from_u32(max_sessions);

        self.insert(Setting::ENABLE_CONNECT_PROTOCOL, VarInt::from_u32(1));
        self.insert(Setting::ENABLE_DATAGRAM, VarInt::from_u32(1));
        self.insert(Setting::ENABLE_DATAGRAM_DEPRECATED, VarInt::from_u32(1));
        self.insert(Setting::WEBTRANSPORT_MAX_SESSIONS, max);

        // TODO remove when 07 is in the wild
        self.insert(Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED, max);
        self.insert(Setting::WEBTRANSPORT_ENABLE_DEPRECATED, VarInt::from_u32(1));
    }

    // Returns the maximum number of sessions supported.
    pub fn supports_webtransport(&self) -> u64 {
        // Sent by Chrome 114.0.5735.198 (July 19, 2023)
        // Setting(1): 65536,              // qpack_max_table_capacity
        // Setting(6): 16384,              // max_field_section_size
        // Setting(7): 100,                // qpack_blocked_streams
        // Setting(51): 1,                 // enable_datagram
        // Setting(16765559): 1            // enable_datagram_deprecated
        // Setting(727725890): 1,          // webtransport_max_sessions_deprecated
        // Setting(4445614305): 454654587, // grease

        // NOTE: The presence of ENABLE_WEBTRANSPORT implies ENABLE_CONNECT is supported.

        let datagram = self
            .get(&Setting::ENABLE_DATAGRAM)
            .or(self.get(&Setting::ENABLE_DATAGRAM_DEPRECATED))
            .map(|v| v.into_inner());

        if datagram != Some(1) {
            return 0;
        }

        // The deprecated (before draft-07) way of enabling WebTransport was to send two parameters.
        // Both would send ENABLE=1 and the server would send MAX_SESSIONS=N to limit the sessions.
        // Now both just send MAX_SESSIONS, and a non-zero value means WebTransport is enabled.

        if let Some(max) = self.get(&Setting::WEBTRANSPORT_MAX_SESSIONS) {
            return max.into_inner();
        }

        let enabled = self
            .get(&Setting::WEBTRANSPORT_ENABLE_DEPRECATED)
            .map(|v| v.into_inner());
        if enabled != Some(1) {
            return 0;
        }

        // Only the server is allowed to set this one, so if it's None we assume it's 1.
        self.get(&Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED)
            .map(|v| v.into_inner())
            .unwrap_or(1)
    }
}

impl Deref for Settings {
    type Target = HashMap<Setting, VarInt>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Settings {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}
