use core::time;
use std::thread;

use log::{info, warn};
use matter::{
    interaction_model::{
        core::{IMStatusCode, OpCode},
        messages::{
            ib::{AttrData, AttrPath, AttrResp, AttrStatus, CmdData, DataVersionFilter},
            msg::{
                self, InvReq, ReadReq, ReportDataMsg, StatusResp, TimedReq, WriteReq, WriteResp,
                WriteRespTag,
            },
        },
    },
    tlv::{self, FromTLV, TLVArray, ToTLV},
    transport::{
        exchange::{self, Exchange},
        session::NocCatIds,
    },
    Matter,
};

use super::{
    attributes::assert_attr_report,
    commands::{assert_inv_response, ExpectedInvResp},
    im_engine::{ImEngine, ImInput, IM_ENGINE_PEER_ID},
};

pub enum WriteResponse<'a> {
    TransactionError,
    TransactionSuccess(&'a [AttrStatus]),
}

pub enum TimedInvResponse<'a> {
    TransactionError(IMStatusCode),
    TransactionSuccess(&'a [ExpectedInvResp]),
}

impl<'a> ImEngine<'a> {
    // Helper for handling Read Req sequences for this file
    pub fn handle_read_reqs(
        &mut self,
        peer_node_id: u64,
        input: &[AttrPath],
        expected: &[AttrResp],
    ) {
        let mut out_buf = [0u8; 400];
        let received = self.gen_read_reqs_output(peer_node_id, input, None, &mut out_buf);
        assert_attr_report(&received, expected)
    }

    pub fn new_with_read_reqs(
        matter: &'a Matter<'a>,
        input: &[AttrPath],
        expected: &[AttrResp],
    ) -> Self {
        let mut im = Self::new(matter);

        let mut out_buf = [0u8; 400];
        let received = im.gen_read_reqs_output(IM_ENGINE_PEER_ID, input, None, &mut out_buf);
        assert_attr_report(&received, expected);

        im
    }

    pub fn gen_read_reqs_output<'b>(
        &mut self,
        peer_node_id: u64,
        input: &[AttrPath],
        dataver_filters: Option<TLVArray<'b, DataVersionFilter>>,
        out_buf: &'b mut [u8],
    ) -> ReportDataMsg<'b> {
        let mut read_req = ReadReq::new(true).set_attr_requests(input);
        read_req.dataver_filters = dataver_filters;

        let mut input = ImInput::new(OpCode::ReadRequest, &read_req);
        input.set_peer_node_id(peer_node_id);

        let (_, out_buf) = self.process(&input, out_buf);

        tlv::print_tlv_list(out_buf);
        let root = tlv::get_root_node_struct(out_buf).unwrap();
        ReportDataMsg::from_tlv(&root).unwrap()
    }

    pub fn handle_write_reqs(
        &mut self,
        peer_node_id: u64,
        peer_cat_ids: Option<&NocCatIds>,
        input: &[AttrData],
        expected: &[AttrStatus],
    ) {
        let mut out_buf = [0u8; 400];
        let write_req = WriteReq::new(false, input);

        let mut input = ImInput::new(OpCode::WriteRequest, &write_req);
        input.set_peer_node_id(peer_node_id);
        if let Some(cat_ids) = peer_cat_ids {
            input.set_cat_ids(cat_ids);
        }

        let (_, out_buf) = self.process(&input, &mut out_buf);

        tlv::print_tlv_list(out_buf);
        let root = tlv::get_root_node_struct(out_buf).unwrap();

        let mut index = 0;
        let response_iter = root
            .find_tag(WriteRespTag::WriteResponses as u32)
            .unwrap()
            .confirm_array()
            .unwrap()
            .enter()
            .unwrap();

        for response in response_iter {
            info!("Validating index {}", index);
            let status = AttrStatus::from_tlv(&response).unwrap();
            assert_eq!(expected[index], status);
            info!("Index {} success", index);
            index += 1;
        }
        assert_eq!(index, expected.len());
    }

    pub fn new_with_write_reqs(
        matter: &'a Matter<'a>,
        input: &[AttrData],
        expected: &[AttrStatus],
    ) -> Self {
        let mut im = Self::new(matter);

        im.handle_write_reqs(IM_ENGINE_PEER_ID, None, input, expected);

        im
    }

    // Helper for handling Invoke Command sequences
    pub fn handle_commands(
        &mut self,
        peer_node_id: u64,
        input: &[CmdData],
        expected: &[ExpectedInvResp],
    ) {
        let mut out_buf = [0u8; 400];
        let req = InvReq {
            suppress_response: Some(false),
            timed_request: Some(false),
            inv_requests: Some(TLVArray::Slice(input)),
        };

        let mut input = ImInput::new(OpCode::InvokeRequest, &req);
        input.set_peer_node_id(peer_node_id);

        let (_, out_buf) = self.process(&input, &mut out_buf);
        tlv::print_tlv_list(out_buf);
        let root = tlv::get_root_node_struct(out_buf).unwrap();
        let resp = msg::InvResp::from_tlv(&root).unwrap();
        assert_inv_response(&resp, expected)
    }

    pub fn new_with_commands(
        matter: &'a Matter<'a>,
        input: &[CmdData],
        expected: &[ExpectedInvResp],
    ) -> Self {
        let mut im = ImEngine::new(matter);

        im.handle_commands(IM_ENGINE_PEER_ID, input, expected);

        im
    }

    fn handle_timed_reqs<'b>(
        &mut self,
        opcode: OpCode,
        request: &dyn ToTLV,
        timeout: u16,
        delay: u16,
        output: &'b mut [u8],
    ) -> (u8, &'b [u8]) {
        // Use the same exchange for all parts of the transaction
        self.exch = Some(Exchange::new(1, 0, exchange::Role::Responder));

        if timeout != 0 {
            // Send Timed Req
            let mut tmp_buf = [0u8; 400];
            let timed_req = TimedReq { timeout };
            let im_input = ImInput::new(OpCode::TimedRequest, &timed_req);
            let (_, out_buf) = self.process(&im_input, &mut tmp_buf);
            tlv::print_tlv_list(out_buf);
        } else {
            warn!("Skipping timed request");
        }

        // Process any delays
        let delay = time::Duration::from_millis(delay.into());
        thread::sleep(delay);

        // Send Write Req
        let input = ImInput::new(opcode, request);
        let (resp_opcode, output) = self.process(&input, output);
        (resp_opcode, output)
    }

    // Helper for handling Write Attribute sequences
    pub fn handle_timed_write_reqs(
        &mut self,
        input: &[AttrData],
        expected: &WriteResponse,
        timeout: u16,
        delay: u16,
    ) {
        let mut out_buf = [0u8; 400];
        let write_req = WriteReq::new(false, input);

        let (resp_opcode, out_buf) = self.handle_timed_reqs(
            OpCode::WriteRequest,
            &write_req,
            timeout,
            delay,
            &mut out_buf,
        );
        tlv::print_tlv_list(out_buf);
        let root = tlv::get_root_node_struct(out_buf).unwrap();

        match expected {
            WriteResponse::TransactionSuccess(t) => {
                assert_eq!(
                    num::FromPrimitive::from_u8(resp_opcode),
                    Some(OpCode::WriteResponse)
                );
                let resp = WriteResp::from_tlv(&root).unwrap();
                assert_eq!(resp.write_responses, t);
            }
            WriteResponse::TransactionError => {
                assert_eq!(
                    num::FromPrimitive::from_u8(resp_opcode),
                    Some(OpCode::StatusResponse)
                );
                let status_resp = StatusResp::from_tlv(&root).unwrap();
                assert_eq!(status_resp.status, IMStatusCode::Timeout);
            }
        }
    }

    pub fn new_with_timed_write_reqs(
        matter: &'a Matter<'a>,
        input: &[AttrData],
        expected: &WriteResponse,
        timeout: u16,
        delay: u16,
    ) -> Self {
        let mut im = ImEngine::new(matter);

        im.handle_timed_write_reqs(input, expected, timeout, delay);

        im
    }

    // Helper for handling Invoke Command sequences
    pub fn handle_timed_commands(
        &mut self,
        input: &[CmdData],
        expected: &TimedInvResponse,
        timeout: u16,
        delay: u16,
        set_timed_request: bool,
    ) {
        let mut out_buf = [0u8; 400];
        let req = InvReq {
            suppress_response: Some(false),
            timed_request: Some(set_timed_request),
            inv_requests: Some(TLVArray::Slice(input)),
        };

        let (resp_opcode, out_buf) =
            self.handle_timed_reqs(OpCode::InvokeRequest, &req, timeout, delay, &mut out_buf);
        tlv::print_tlv_list(out_buf);
        let root = tlv::get_root_node_struct(out_buf).unwrap();

        match expected {
            TimedInvResponse::TransactionSuccess(t) => {
                assert_eq!(
                    num::FromPrimitive::from_u8(resp_opcode),
                    Some(OpCode::InvokeResponse)
                );
                let resp = msg::InvResp::from_tlv(&root).unwrap();
                assert_inv_response(&resp, t)
            }
            TimedInvResponse::TransactionError(e) => {
                assert_eq!(
                    num::FromPrimitive::from_u8(resp_opcode),
                    Some(OpCode::StatusResponse)
                );
                let status_resp = StatusResp::from_tlv(&root).unwrap();
                assert_eq!(status_resp.status, *e);
            }
        }
    }

    pub fn new_with_timed_commands(
        matter: &'a Matter<'a>,
        input: &[CmdData],
        expected: &TimedInvResponse,
        timeout: u16,
        delay: u16,
        set_timed_request: bool,
    ) -> Self {
        let mut im = ImEngine::new(matter);

        im.handle_timed_commands(input, expected, timeout, delay, set_timed_request);

        im
    }
}