use std::cell::OnceCell;

use sark_core::error::Result;
use sark_core::http::LocalFrameBytes;
use sark_core::http::head::{HeadInput, is_ascii_ws};

use crate::service::HeaderValue;

pub(super) struct InputHeaderValue<'a, I: HeadInput + ?Sized> {
    input: &'a I,
    start: usize,
    end: usize,
    local: OnceCell<LocalFrameBytes>,
}

impl<'a, I: HeadInput + ?Sized> InputHeaderValue<'a, I> {
    pub(super) fn new(input: &'a I, range: std::ops::Range<usize>) -> Self {
        Self {
            input,
            start: range.start,
            end: range.end,
            local: OnceCell::new(),
        }
    }

    fn span(&self) -> std::ops::Range<usize> {
        self.start..self.end
    }

    fn local(&self) -> &LocalFrameBytes {
        self.local
            .get_or_init(|| match self.input.copy_range_local(self.span()) {
                Some(v) => v,
                None => {
                    debug_assert!(
                        false,
                        "header value invariant: input range must be readable"
                    );
                    LocalFrameBytes::from_shared(o3::buffer::Shared::copy_from_slice(b""))
                }
            })
    }

    fn eq_input(&self, expected: &[u8], ignore_ascii_case: bool) -> bool {
        let range = self.span();
        if expected.len() != range.end.saturating_sub(range.start) {
            return false;
        }
        if let Some(bytes) = self.input.slice_range(range.clone()) {
            return if ignore_ascii_case {
                bytes.eq_ignore_ascii_case(expected)
            } else {
                bytes == expected
            };
        }
        let mut matched = true;
        let mut off = 0usize;
        self.input.for_each_slice(range, |chunk| {
            if !matched {
                return;
            }
            let end = off + chunk.len();
            let want = &expected[off..end];
            matched = if ignore_ascii_case {
                chunk.eq_ignore_ascii_case(want)
            } else {
                chunk == want
            };
            off = end;
        });
        matched && off == expected.len()
    }

    fn parse_decimal_u64(&self) -> Result<u64> {
        let range = self.span();
        if range.start >= range.end {
            return Err(sark_core::error::Error::invalid_integer_header());
        }
        let mut saw_digit = false;
        let mut trailing_ws = false;
        let mut out = 0u64;
        let mut invalid = None;
        self.input.for_each_slice(range, |bytes| {
            if invalid.is_some() {
                return;
            }
            for &b in bytes {
                if is_ascii_ws(b) {
                    if saw_digit {
                        trailing_ws = true;
                    }
                    continue;
                }
                if !b.is_ascii_digit() || trailing_ws {
                    invalid = Some(sark_core::error::Error::invalid_integer_header());
                    return;
                }
                saw_digit = true;
                let digit = u64::from(b - b'0');
                out = match out.checked_mul(10).and_then(|v| v.checked_add(digit)) {
                    Some(v) => v,
                    None => {
                        invalid = Some(sark_core::error::Error::invalid_integer_header());
                        return;
                    }
                };
            }
        });
        if let Some(err) = invalid {
            return Err(err);
        }
        if !saw_digit {
            return Err(sark_core::error::Error::invalid_integer_header());
        }
        Ok(out)
    }
}

impl<I: HeadInput + ?Sized> HeaderValue for InputHeaderValue<'_, I> {
    fn len(&self) -> usize {
        self.end.saturating_sub(self.start)
    }

    fn eq_bytes(&self, expected: &[u8]) -> bool {
        self.eq_input(expected, false)
    }

    fn eq_ignore_ascii_case(&self, expected: &[u8]) -> bool {
        self.eq_input(expected, true)
    }

    fn as_range(&self) -> std::ops::Range<usize> {
        self.span()
    }

    fn copy_local(&self) -> sark_core::http::LocalFrameBytes {
        if let Some(bytes) = self.input.slice_range(self.span()) {
            return LocalFrameBytes::from_shared(o3::buffer::Shared::copy_from_slice(bytes));
        }
        self.local().clone()
    }

    fn parse_usize(&self) -> Result<usize> {
        let out = self.parse_decimal_u64()?;
        usize::try_from(out).map_err(|_| sark_core::error::Error::invalid_integer_header())
    }

    fn parse_u64(&self) -> Result<u64> {
        self.parse_decimal_u64()
    }
}
