use std::fmt;
use std::iter::FromIterator;
use std::marker::PhantomData;

use bytes::BytesMut;
use http::HeaderValue;

use crate::util::TryFromValues;
use crate::Error;

// A single `HeaderValue` that can flatten multiple values with commas.
#[derive(Clone, PartialEq, Eq, Hash)]
pub(crate) struct FlatCsv<Sep = Comma> {
    pub(crate) value: HeaderValue,
    _marker: PhantomData<Sep>,
}

pub(crate) trait Separator {
    const BYTE: u8;
    const CHAR: char;
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Comma {}

impl Separator for Comma {
    const BYTE: u8 = b',';
    const CHAR: char = ',';
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum SemiColon {}

impl Separator for SemiColon {
    const BYTE: u8 = b';';
    const CHAR: char = ';';
}

impl<Sep: Separator> FlatCsv<Sep> {
    pub(crate) fn iter(&self) -> impl Iterator<Item = &str> {
        self.value.to_str().ok().into_iter().flat_map(|value_str| {
            let mut in_quotes = false;
            value_str
                .split(move |c| {
                    #[allow(clippy::collapsible_else_if)]
                    if in_quotes {
                        if c == '"' {
                            in_quotes = false;
                        }
                        false // dont split
                    } else {
                        if c == Sep::CHAR {
                            true // split
                        } else {
                            if c == '"' {
                                in_quotes = true;
                            }
                            false // dont split
                        }
                    }
                })
                .map(|item| item.trim())
        })
    }
}

impl<Sep: Separator> TryFromValues for FlatCsv<Sep> {
    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error>
    where
        I: Iterator<Item = &'i HeaderValue>,
    {
        let flat = values.collect();
        Ok(flat)
    }
}

impl<Sep> From<HeaderValue> for FlatCsv<Sep> {
    fn from(value: HeaderValue) -> Self {
        FlatCsv {
            value,
            _marker: PhantomData,
        }
    }
}

impl<'a, Sep> From<&'a FlatCsv<Sep>> for HeaderValue {
    fn from(flat: &'a FlatCsv<Sep>) -> HeaderValue {
        flat.value.clone()
    }
}

impl<Sep> fmt::Debug for FlatCsv<Sep> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(&self.value, f)
    }
}

impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv<Sep> {
    fn from_iter<I>(iter: I) -> Self
    where
        I: IntoIterator<Item = &'a HeaderValue>,
    {
        let mut values = iter.into_iter();

        // Common case is there is only 1 value, optimize for that
        if let (1, Some(1)) = values.size_hint() {
            return values
                .next()
                .expect("size_hint claimed 1 item")
                .clone()
                .into();
        }

        // Otherwise, there are multiple, so this should merge them into 1.
        let mut buf = values
            .next()
            .cloned()
            .map(|val| BytesMut::from(val.as_bytes()))
            .unwrap_or_default();

        for val in values {
            buf.extend_from_slice(&[Sep::BYTE, b' ']);
            buf.extend_from_slice(val.as_bytes());
        }

        let val = HeaderValue::from_maybe_shared(buf.freeze())
            .expect("comma separated HeaderValues are valid");

        val.into()
    }
}

// TODO: would be great if there was a way to de-dupe these with above
impl<Sep: Separator> FromIterator<HeaderValue> for FlatCsv<Sep> {
    fn from_iter<I>(iter: I) -> Self
    where
        I: IntoIterator<Item = HeaderValue>,
    {
        let mut values = iter.into_iter();

        // Common case is there is only 1 value, optimize for that
        if let (1, Some(1)) = values.size_hint() {
            return values.next().expect("size_hint claimed 1 item").into();
        }

        // Otherwise, there are multiple, so this should merge them into 1.
        let mut buf = values
            .next()
            .map(|val| BytesMut::from(val.as_bytes()))
            .unwrap_or_default();

        for val in values {
            buf.extend_from_slice(&[Sep::BYTE, b' ']);
            buf.extend_from_slice(val.as_bytes());
        }

        let val = HeaderValue::from_maybe_shared(buf.freeze())
            .expect("comma separated HeaderValues are valid");

        val.into()
    }
}

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

    #[test]
    fn comma() {
        let val = HeaderValue::from_static("aaa, b; bb, ccc");
        let csv = FlatCsv::<Comma>::from(val);

        let mut values = csv.iter();
        assert_eq!(values.next(), Some("aaa"));
        assert_eq!(values.next(), Some("b; bb"));
        assert_eq!(values.next(), Some("ccc"));
        assert_eq!(values.next(), None);
    }

    #[test]
    fn semicolon() {
        let val = HeaderValue::from_static("aaa; b, bb; ccc");
        let csv = FlatCsv::<SemiColon>::from(val);

        let mut values = csv.iter();
        assert_eq!(values.next(), Some("aaa"));
        assert_eq!(values.next(), Some("b, bb"));
        assert_eq!(values.next(), Some("ccc"));
        assert_eq!(values.next(), None);
    }

    #[test]
    fn quoted_text() {
        let val = HeaderValue::from_static("foo=\"bar,baz\", sherlock=holmes");
        let csv = FlatCsv::<Comma>::from(val);

        let mut values = csv.iter();
        assert_eq!(values.next(), Some("foo=\"bar,baz\""));
        assert_eq!(values.next(), Some("sherlock=holmes"));
        assert_eq!(values.next(), None);
    }
}
