use crate::{
    application_protocol::primitives::data_value::ApplicationDataValueWrite,
    common::{
        error::Error,
        helper::{
            decode_context_object_id, decode_context_property_id, decode_unsigned,
            encode_closing_tag, encode_context_enumerated, encode_context_object_id,
            encode_context_unsigned, encode_opening_tag, get_tagged_body_for_tag,
        },
        io::{Reader, Writer},
        object_id::ObjectId,
        property_id::PropertyId,
        spec::BACNET_ARRAY_ALL,
        tag::{Tag, TagNumber},
    },
};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

/// Represents a single property write request with its value
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WritePropertyRequest<'a> {
    pub property_id: PropertyId,
    pub array_index: Option<u32>,
    pub value: ApplicationDataValueWrite<'a>,
    pub priority: Option<u8>,
}

impl<'a> WritePropertyRequest<'a> {
    const TAG_PROPERTY_ID: u8 = 0;
    const TAG_ARRAY_INDEX: u8 = 1;
    const TAG_VALUE: u8 = 2;
    const TAG_PRIORITY: u8 = 3;
    const LOWEST_PRIORITY: u8 = 16;

    pub fn new(
        property_id: PropertyId,
        array_index: Option<u32>,
        value: ApplicationDataValueWrite<'a>,
        priority: Option<u8>,
    ) -> Self {
        Self {
            property_id,
            array_index,
            value,
            priority,
        }
    }

    pub fn encode(&self, writer: &mut Writer, _object_id: &ObjectId) {
        // property_id
        encode_context_enumerated(writer, Self::TAG_PROPERTY_ID, &self.property_id);

        // array_index (optional)
        if let Some(array_index) = self.array_index {
            encode_context_unsigned(writer, Self::TAG_ARRAY_INDEX, array_index);
        }

        // value
        encode_opening_tag(writer, Self::TAG_VALUE);
        self.value.encode(writer);
        encode_closing_tag(writer, Self::TAG_VALUE);

        // priority 0-16 (16 being lowest priority)
        if let Some(priority) = self.priority {
            let priority = priority.min(Self::LOWEST_PRIORITY) as u32;
            encode_context_unsigned(writer, Self::TAG_PRIORITY, priority);
        }
    }

    #[cfg_attr(feature = "alloc", bacnet_macros::remove_lifetimes_from_fn_args)]
    pub fn decode(
        reader: &mut Reader,
        buf: &'a [u8],
        object_id: &ObjectId,
    ) -> Result<Self, Error> {
        let property_id = decode_context_property_id(
            reader,
            buf,
            Self::TAG_PROPERTY_ID,
            "WritePropertyRequest decode property_id",
        )?;

        // array_index (optional)
        let mut tag = Tag::decode(reader, buf)?;
        let mut array_index = None;
        if let TagNumber::ContextSpecific(Self::TAG_ARRAY_INDEX) = tag.number {
            let array_index_tmp = decode_unsigned(tag.value, reader, buf)? as u32;
            if array_index_tmp != BACNET_ARRAY_ALL {
                array_index = Some(array_index_tmp);
            }
            tag = Tag::decode(reader, buf)?;
        }

        // value
        tag.expect_number(
            "WritePropertyRequest decode value",
            TagNumber::ContextSpecificOpening(Self::TAG_VALUE),
        )?;
        let value = ApplicationDataValueWrite::decode(object_id, &property_id, reader, buf)?;
        Tag::decode_expected(
            reader,
            buf,
            TagNumber::ContextSpecificClosing(Self::TAG_VALUE),
            "WritePropertyRequest decode value",
        )?;

        // priority (optional)
        let mut priority = None;
        if !reader.eof() {
            let tag_result = Tag::decode(reader, buf);
            if let Ok(tag) = tag_result {
                if let TagNumber::ContextSpecific(Self::TAG_PRIORITY) = tag.number {
                    let priority_val = decode_unsigned(tag.value, reader, buf)? as u8;
                    priority = if priority_val == Self::LOWEST_PRIORITY {
                        None
                    } else {
                        Some(priority_val)
                    };
                }
            }
        }

        Ok(Self {
            property_id,
            array_index,
            value,
            priority,
        })
    }
}

/// Represents a single object with multiple property write requests
#[cfg(not(feature = "alloc"))]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WritePropertyMultipleObject<'a> {
    pub object_id: ObjectId,
    pub writes: &'a [WritePropertyRequest<'a>],
    buf: &'a [u8],
}

#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WritePropertyMultipleObject<'a> {
    pub object_id: ObjectId,
    pub writes: Vec<WritePropertyRequest<'a>>,
}

impl<'a> WritePropertyMultipleObject<'a> {
    #[cfg(not(feature = "alloc"))]
    pub fn new(object_id: ObjectId, writes: &'a [WritePropertyRequest<'a>]) -> Self {
        Self {
            object_id,
            writes,
            buf: &[],
        }
    }

    #[cfg(feature = "alloc")]
    pub fn new(object_id: ObjectId, writes: Vec<WritePropertyRequest<'a>>) -> Self {
        Self { object_id, writes }
    }

    pub fn encode(&self, writer: &mut Writer) {
        // object_id
        encode_context_object_id(writer, 0, &self.object_id);

        // list of property writes
        encode_opening_tag(writer, 1);

        for write in self.writes.iter() {
            write.encode(writer, &self.object_id);
        }

        encode_closing_tag(writer, 1);
    }

    #[cfg(not(feature = "alloc"))]
    #[cfg_attr(feature = "alloc", bacnet_macros::remove_lifetimes_from_fn_args)]
    pub fn decode(reader: &mut Reader, buf: &'a [u8]) -> Result<Self, Error> {
        let object_id = decode_context_object_id(
            reader,
            buf,
            0,
            "WritePropertyMultipleObject decode object_id",
        )?;

        let buf = get_tagged_body_for_tag(
            reader,
            buf,
            1,
            "WritePropertyMultipleObject decode list of writes",
        )?;

        Ok(WritePropertyMultipleObject {
            object_id,
            writes: &[],
            buf,
        })
    }

    #[cfg(feature = "alloc")]
    pub fn decode(reader: &mut Reader, buf: &[u8]) -> Result<Self, Error> {
        let object_id = decode_context_object_id(
            reader,
            buf,
            0,
            "WritePropertyMultipleObject decode object_id",
        )?;

        let inner_buf = get_tagged_body_for_tag(
            reader,
            buf,
            1,
            "WritePropertyMultipleObject decode list of writes",
        )?;

        let mut inner_reader = Reader::new_with_len(inner_buf.len());
        let mut writes = Vec::new();

        while !inner_reader.eof() {
            let write_request =
                WritePropertyRequest::decode(&mut inner_reader, inner_buf, &object_id)?;
            writes.push(write_request);
        }

        Ok(Self::new(object_id, writes))
    }
}

/// Represents the complete WritePropertyMultiple request
#[cfg(not(feature = "alloc"))]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WritePropertyMultiple<'a> {
    pub objects: &'a [WritePropertyMultipleObject<'a>],
    buf: &'a [u8],
}

#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WritePropertyMultiple<'a> {
    pub objects: Vec<WritePropertyMultipleObject<'a>>,
}

impl<'a> WritePropertyMultiple<'a> {
    #[cfg(not(feature = "alloc"))]
    pub fn new(objects: &'a [WritePropertyMultipleObject<'a>]) -> Self {
        Self {
            objects,
            buf: &[],
        }
    }

    #[cfg(feature = "alloc")]
    pub fn new(objects: Vec<WritePropertyMultipleObject<'a>>) -> Self {
        Self { objects }
    }

    pub fn encode(&self, writer: &mut Writer) {
        for object in self.objects.iter() {
            object.encode(writer);
        }
    }

    #[cfg(not(feature = "alloc"))]
    #[cfg_attr(feature = "alloc", bacnet_macros::remove_lifetimes_from_fn_args)]
    pub fn decode(reader: &mut Reader, buf: &'a [u8]) -> Result<Self, Error> {
        let buf = &buf[reader.index..reader.end];
        Ok(Self {
            buf,
            objects: &[],
        })
    }

    #[cfg(feature = "alloc")]
    pub fn decode(reader: &mut Reader, buf: &[u8]) -> Result<Self, Error> {
        let inner_buf = &buf[reader.index..reader.end];
        let mut inner_reader = Reader::new_with_len(inner_buf.len());
        let mut objects = Vec::new();

        while !inner_reader.eof() {
            let object = WritePropertyMultipleObject::decode(&mut inner_reader, inner_buf)?;
            objects.push(object);
        }

        Ok(Self::new(objects))
    }
}

/// Iterator for WritePropertyRequest (no_std version)
#[cfg(not(feature = "alloc"))]
pub struct WritePropertyRequestIter<'a> {
    object_id: ObjectId,
    reader: Reader,
    buf: &'a [u8],
}

#[cfg(not(feature = "alloc"))]
impl<'a> Iterator for WritePropertyRequestIter<'a> {
    type Item = Result<WritePropertyRequest<'a>, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.reader.eof() {
            return None;
        }

        Some(WritePropertyRequest::decode(
            &mut self.reader,
            self.buf,
            &self.object_id,
        ))
    }
}

/// Iterator for WritePropertyMultipleObject (no_std version)
#[cfg(not(feature = "alloc"))]
pub struct WritePropertyMultipleObjectIter<'a> {
    buf: &'a [u8],
    reader: Reader,
}

#[cfg(not(feature = "alloc"))]
impl<'a> Iterator for WritePropertyMultipleObjectIter<'a> {
    type Item = Result<WritePropertyMultipleObject<'a>, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.reader.eof() {
            return None;
        }

        let object_with_writes = WritePropertyMultipleObject::decode(&mut self.reader, self.buf);
        Some(object_with_writes)
    }
}

#[cfg(not(feature = "alloc"))]
impl<'a> IntoIterator for &'_ WritePropertyMultiple<'a> {
    type Item = Result<WritePropertyMultipleObject<'a>, Error>;

    type IntoIter = WritePropertyMultipleObjectIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        WritePropertyMultipleObjectIter {
            buf: self.buf,
            reader: Reader::new_with_len(self.buf.len()),
        }
    }
}
