// This file is part of Astarte.
//
// Copyright 2025, 2026 SECO Mind Srl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! Done and Done2, Type 70, 71
//!
//! Last messages in the TO2 Protocol.
//!
//! ### Done, Type 70
//!
//! From Device to Owner Onboarding Service:
//!
//! Message Format - after decryption and verification:
//!
//! ```cddl
//! TO2.Done = [
//!     NonceTO2ProveDv;; Nonce generated by Owner Onboarding Service
//!                    ;; ...and sent to Device ROE in Msg TO2.ProveOVHdr
//! ]
//! ```
//!
//! Indicates successful completion of the Transfer of Ownership.
//!
//! ### Done2, Type 71
//!
//! From Owner Onboarding Service to Device:
//!
//! Message Format - after decryption and verification:
//!
//! ```cddl
//! TO2.Done2 = [
//!     NonceTO2SetupDv
//! ]
//! ```
//!
//! This message provides an opportunity for a final ACK after the Owner has invoked the System Info block to establish agent-to-server communications between the Device and its final Owner.
//!
//! When possible, the TO2.Done2 should be delayed until the Device has established agent-to-server communications, allowing a FIDO Device Onboard error to occur when such communications fail.
// TODO: implement last parto of the TO2, by initializing the connection to Astarte before receiving
//       the TO2

use std::io::Write;

use serde::{Deserialize, Serialize};

use crate::Error;
use crate::error::ErrorKind;
use crate::v101::{ClientMessage, Message, Msgtype, NonceTo2ProveDv, NonceTo2SetupDv};

/// ```cddl
/// TO2.Done = [
///     NonceTO2ProveDv;; Nonce generated by Owner Onboarding Service
///                    ;; ...and sent to Device ROE in Msg TO2.ProveOVHdr
/// ]
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Done {
    pub(crate) nonce_to2_prove_dv: NonceTo2ProveDv,
}

impl Done {
    /// Create the done message
    pub fn new(nonce_to2_prove_dv: NonceTo2ProveDv) -> Self {
        Self { nonce_to2_prove_dv }
    }
}

impl Serialize for Done {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let Self { nonce_to2_prove_dv } = self;

        (nonce_to2_prove_dv,).serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Done {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let (nonce_to2_prove_dv,) = Deserialize::deserialize(deserializer)?;

        Ok(Self { nonce_to2_prove_dv })
    }
}

impl Message for Done {
    const MSG_TYPE: Msgtype = 70;

    fn decode(buf: &[u8]) -> Result<Self, Error> {
        ciborium::from_reader(buf).map_err(|err| {
            #[cfg(feature = "tracing")]
            tracing::error!(error = %err, "couldn't decode TO2.Done");

            Error::new(ErrorKind::Decode, "the TO2.Done")
        })
    }

    fn encode<W>(&self, write: &mut W) -> Result<(), Error>
    where
        W: Write,
    {
        ciborium::into_writer(self, write).map_err(|err| {
            #[cfg(feature = "tracing")]
            tracing::error!(error = %err, "couldn't encode TO2.Done");

            Error::new(ErrorKind::Encode, "the TO2.Done")
        })
    }
}

impl ClientMessage for Done {
    type Response<'a> = Done2;
}

/// ```cddl
/// TO2.Done = [
///     NonceTO2ProveDv;; Nonce generated by Owner Onboarding Service
///                    ;; ...and sent to Device ROE in Msg TO2.ProveOVHdr
/// ]
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Done2 {
    pub(crate) nonce_to2_setup_dv: NonceTo2SetupDv,
}

impl Done2 {
    /// Return the nonce
    pub fn nonce(&self) -> &NonceTo2SetupDv {
        &self.nonce_to2_setup_dv
    }
}

impl Serialize for Done2 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let Self { nonce_to2_setup_dv } = self;

        (nonce_to2_setup_dv,).serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Done2 {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let (nonce_to2_setup_dv,) = Deserialize::deserialize(deserializer)?;

        Ok(Self { nonce_to2_setup_dv })
    }
}

impl Message for Done2 {
    const MSG_TYPE: Msgtype = 71;

    fn decode(buf: &[u8]) -> Result<Self, Error> {
        ciborium::from_reader(buf).map_err(|err| {
            #[cfg(feature = "tracing")]
            tracing::error!(error = %err, "couldn't decode TO2.Done2");

            Error::new(ErrorKind::Decode, "the TO2.Done")
        })
    }

    fn encode<W>(&self, write: &mut W) -> Result<(), Error>
    where
        W: Write,
    {
        ciborium::into_writer(self, write).map_err(|err| {
            #[cfg(feature = "tracing")]
            tracing::error!(error = %err, "couldn't encode TO2.Done2");

            Error::new(ErrorKind::Encode, "the TO2.Done")
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::tests::insta_settings;
    use crate::v101::Nonce;

    use super::*;

    #[test]
    fn done_roundtrip() {
        let nonce = Nonce::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
        let done = Done::new(NonceTo2ProveDv(nonce));

        let mut buf = Vec::new();

        done.encode(&mut buf).unwrap();

        let res = Done::decode(&buf).unwrap();

        assert_eq!(res, done);

        insta_settings!({
            insta::assert_binary_snapshot!(".cbor", buf);
        });
    }

    #[test]
    fn done_2_roundtrip() {
        let nonce = Nonce::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
        let done = Done2 {
            nonce_to2_setup_dv: NonceTo2SetupDv(nonce),
        };

        let mut buf = Vec::new();

        done.encode(&mut buf).unwrap();

        let res = Done2::decode(&buf).unwrap();

        assert_eq!(res, done);

        insta_settings!({
            insta::assert_binary_snapshot!(".cbor", buf);
        });

        assert_eq!(*res.nonce(), NonceTo2SetupDv(nonce));
    }
}
