use ipmi_rs_core::app::auth::AuthenticationAlgorithm;

use crate::rmcp::{
    v2_0::{crypto::sha1::Sha1Hmac, ReadError, WriteError},
    Message, OpenSessionResponse as OSR, RakpMessage1 as RM1, RakpMessage2 as RM2,
};

use super::{keys::Keys, SubState};

pub struct CryptoState {
    password: Vec<u8>,
    kg: Option<[u8; 20]>,
    state: SubState,
}

impl core::fmt::Debug for CryptoState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CryptoState")
            .field("password", &"<redacted>")
            .field("kg", &"<redacted>")
            .field("state", &self.state)
            .finish()
    }
}

impl Default for CryptoState {
    fn default() -> Self {
        Self {
            password: Vec::new(),
            kg: None,
            state: SubState::empty(),
        }
    }
}

impl CryptoState {
    fn kg(&self) -> &[u8] {
        self.kg
            .as_ref()
            .map(|v| &v[..])
            .unwrap_or(self.password.as_ref())
    }
}

impl CryptoState {
    pub fn new(kg: Option<[u8; 20]>, password: &[u8]) -> Self {
        Self {
            kg,
            password: password.to_vec(),
            state: SubState::empty(),
        }
    }

    pub fn calculate_rakp3_data(&mut self, osr: &OSR, m1: &RM1, m2: &RM2) -> Option<Vec<u8>> {
        match osr.authentication_payload {
            AuthenticationAlgorithm::RakpNone => todo!(),
            AuthenticationAlgorithm::RakpHmacSha1 => self.validate_hmac_sha1(osr, m1, m2),
            AuthenticationAlgorithm::RakpHmacMd5 => todo!(),
            AuthenticationAlgorithm::RakpHmacSha256 => todo!(),
        }
    }

    pub fn verify(
        &self,
        algorithm: AuthenticationAlgorithm,
        remote_console_random_number: &[u8; 16],
        managed_system_session_id: u32,
        managed_system_guid: &[u8; 16],
        integrity_check_value: &[u8],
    ) -> bool {
        match algorithm {
            AuthenticationAlgorithm::RakpNone => integrity_check_value.is_empty(),
            AuthenticationAlgorithm::RakpHmacSha1 => {
                let integrity = &Sha1Hmac::new(&self.state.keys.sik)
                    .feed(remote_console_random_number)
                    .feed(&managed_system_session_id.to_le_bytes())
                    .feed(managed_system_guid)
                    .finalize()[..12];

                integrity_check_value == integrity
            }
            AuthenticationAlgorithm::RakpHmacMd5 => todo!(),
            AuthenticationAlgorithm::RakpHmacSha256 => todo!(),
        }
    }

    fn validate_hmac_sha1(&mut self, osr: &OSR, m1: &RM1, m2: &RM2) -> Option<Vec<u8>> {
        let privilege_level_byte = u8::from(m1.requested_maximum_privilege_level);

        let hmac_output = Sha1Hmac::new(&self.password)
            .feed(&m2.remote_console_session_id.get().to_le_bytes())
            .feed(&m1.managed_system_session_id.get().to_le_bytes())
            .feed(&m1.remote_console_random_number)
            .feed(&m2.managed_system_random_number)
            .feed(&m2.managed_system_guid)
            .feed(&[privilege_level_byte, m1.username.len()])
            .feed(m1.username)
            .finalize();

        if hmac_output == m2.key_exchange_auth_code {
            let sik = Sha1Hmac::new(self.kg())
                .feed(&m1.remote_console_random_number)
                .feed(&m2.managed_system_random_number)
                .feed(&[privilege_level_byte, m1.username.len()])
                .feed(m1.username)
                .finalize();

            let output = Sha1Hmac::new(&self.password)
                .feed(&m2.managed_system_random_number)
                .feed(&m2.remote_console_session_id.get().to_le_bytes())
                .feed(&[privilege_level_byte, m1.username.len()])
                .feed(m1.username)
                .finalize();

            let new_state = SubState {
                keys: Keys::from_sik(sik),
                confidentiality_algorithm: osr.confidentiality_payload,
                integrity_algorithm: osr.integrity_payload,
            };

            self.state = new_state;

            Some(output.to_vec())
        } else {
            None
        }
    }
}

impl CryptoState {
    pub fn read_payload(&mut self, data: &mut [u8]) -> Result<Message, ReadError> {
        self.state.read_payload(data)
    }

    pub fn write_unencrypted(message: &Message, buffer: &mut Vec<u8>) -> Result<(), WriteError> {
        SubState::empty().write_payload(message, buffer)
    }

    pub fn write_message(
        &mut self,
        message: &Message,
        buffer: &mut Vec<u8>,
    ) -> Result<(), WriteError> {
        self.state.write_payload(message, buffer)
    }
}
