/*-
 * Scram-rs - a SCRAM authentification authorization library
 * 
 * Copyright (C) 2021  Aleksandr Morozov
 * Copyright (C) 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

#[cfg(feature = "std")]
use std::str;
#[cfg(not(feature = "std"))]
use core::str;

#[cfg(feature = "std")]
use std::marker::PhantomData;
#[cfg(not(feature = "std"))]
use core::marker::PhantomData;

#[cfg(not(feature = "std"))]
use alloc::string::String;

#[cfg(not(feature = "std"))]
use alloc::boxed::Box;

#[cfg(not(feature = "std"))]
use crate::alloc::string::ToString;

#[cfg(not(feature = "std"))]
use core::borrow::Borrow;

#[cfg(feature = "std")]
use std::borrow::Borrow;

#[cfg(not(feature = "std"))]
use core::hash::Hash;

#[cfg(feature = "std")]
use std::hash::Hash;

#[cfg(not(feature = "std"))]
use core::hash::Hasher;

#[cfg(feature = "std")]
use std::hash::Hasher;

#[cfg(not(feature = "std"))]
use core::cmp::Ordering;

#[cfg(feature = "std")]
use std::cmp::Ordering;

#[cfg(not(feature = "std"))]
use core::num::NonZeroUsize;

#[cfg(feature = "std")]
use std::num::NonZeroUsize;



use base64::Engine;
use base64::engine::general_purpose;

pub use super::scram_dyn::ScramServerDyn;
use super::scram_cbh::ScramCbHelper;
use super::{ScramResultServer, ScramServerError};
use super::scram_error::{ScramResult, ScramErrorCode};
use super::{scram_error, scram_error_map};
use super::scram_cb::ChannelBindType;
use super::scram_auth::{ScramPassword, ScramAuthServer};
use super::scram_hashing::ScramHashing;
use super::scram_common::ScramType;
use super::scram_state::ScramState;
use super::scram_parser::*;
use super::scram::ScramNonce;



/// # A Scram Server. 
/// 
/// Should be used on the server side. Can be coverted into boxed `dyn` object.
/// 
/// ## Generics
/// 
/// * `S` - [ScramHashing] A hashing algorith which will be used. This crate 
///     does not provides a methods for hashing algorith negoatiations. It 
///     should be known prior to constructiong the instance.
/// 
/// * `A` - [ScramAuthServer] a credentials storage.
/// 
/// * `B` - [ScramCbHelper] a channel binding helper.
/// 
/// ```text
/// If it is required to support `channel binding`, the crate does not provide
/// a helpers to obtain required information. You must find out how to extract 
/// endpoint certificate from TLS connection.
///     i.e native_tls::TlsStream::tls_server_end_point()  
/// ```
#[derive(Debug)]
pub struct SyncScramServer<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper>
{
    /// Instance ID
    id: usize,

    /// The hasher which will be used: SHA-1 SHA-256
    hasher: PhantomData<S>,

    /// The Auth backend which handles user search
    auth: A,

    /// The current instance type
    st: &'static ScramType,

    /// username n=
    username: Option<String>,

    /// athzid a= 
    authzid: Option<String>,

    /// Returned password with status found/notfound
    sp: ScramPassword,

    /// Generated server nonce
    server_nonce: String,

    /// Current scram state
    state: ScramState,

    /// Received channel binding opt from client
    cli_chanbind: ChannelBindType,

    /// A callback to support channel bind mechanism
    chanbind_helper: B,

    /// Ignores the extensions which may appear after the base message items.
    ignore_exts: bool,
}

impl<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper> Eq 
for  SyncScramServer<S, A, B> {}

impl<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper> PartialEq 
for  SyncScramServer<S, A, B>
{
    fn eq(&self, other: &Self) -> bool 
    {
        return self.id == other.id;
    }
}

impl<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper> Ord 
for  SyncScramServer<S, A, B>
{
    fn cmp(&self, other: &Self) -> Ordering 
    {
        return self.id.cmp(&other.id);
    }
}

impl<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper> PartialOrd 
for  SyncScramServer<S, A, B>
{
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> 
    {
        return Some(self.cmp(other));
    }
}

impl<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper> Borrow<usize> 
for  SyncScramServer<S, A, B>
{
    fn borrow(&self) -> &usize 
    {
        return &self.id;
    }
}

impl<'ss> Eq for Box<dyn ScramServerDyn + 'ss>
{

}

impl<'ss> PartialEq for Box<dyn ScramServerDyn + 'ss>
{
    fn eq(&self, other: &Self) -> bool 
    {
        return self.as_ref().borrow() == other.as_ref().borrow();
    }
}

impl<'ss> PartialEq<usize> for Box<dyn ScramServerDyn + 'ss>
{
    fn eq(&self, other: &usize) -> bool 
    {
        return self.as_ref().borrow() == other;
    }
}

impl<'ss> Ord for Box<dyn ScramServerDyn + 'ss>
{
    fn cmp(&self, other: &Self) -> Ordering 
    {
        return self.as_ref().borrow().cmp(other.as_ref().borrow());
    }
}

impl<'ss> PartialOrd for Box<dyn ScramServerDyn + 'ss>
{
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> 
    {
        return Some(self.cmp(other));
    }
}

impl<'ss> PartialOrd<usize> for Box<dyn ScramServerDyn + 'ss>
{
    fn partial_cmp(&self, other: &usize) -> Option<Ordering> 
    {
        return Some(self.as_ref().borrow().cmp(other));
    }
}

impl<'ss> Hash for Box<dyn ScramServerDyn + 'ss>
{
    fn hash<H: Hasher>(&self, state: &mut H) 
    {
        self.as_ref().borrow().hash(state);
    }
}


impl<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper> ScramServerDyn 
for SyncScramServer<S, A, B>
{
    fn get_id(&self) -> usize 
    {
        return self.id;    
    }

    fn get_auth_username(&self) -> Option<&String>
    {
        return self.username.as_ref();
    }

    fn get_auth_authzid(&self) -> Option<&String>
    {
        return self.authzid.as_ref();
    }

    fn parse_response_base64(&mut self, input: &[u8]) -> ScramResultServer
    {
        let decoded = 
            match general_purpose::STANDARD.decode(input)
                .map_err(|e| 
                    scram_error_map!(ScramErrorCode::MalformedScramMsg, ScramServerError::InvalidEncoding,
                        "base64 decoding of client response failed with error, '{}'", e)
                )
                .map_err(|e| ScramResultServer::Error(e))
            {
                Ok(r) => r,
                Err(e) => return e
            };
                

        let dec_utf8 = 
            match str::from_utf8(&decoded)
                    .map_err(|e| 
                        scram_error_map!(
                            ScramErrorCode::MalformedScramMsg, ScramServerError::InvalidEncoding,
                            "base64 decoded response contains invalid UTF-8 seq, '{}'", e
                        )
                    )
            {
                Ok(r) => r,
                Err(e) => 
                    return ScramResultServer::Error(e)
            };

        return self.parse_response(dec_utf8);
    }

    fn parse_response(&mut self, resp: &str) -> ScramResultServer
    {
        match self.parse_response_internal(resp)
        {
            Ok(r) =>
            {
                if self.state == ScramState::Completed
                {
                    return ScramResultServer::Final(r);
                }
                else
                {
                    return ScramResultServer::Data(r);
                }
            },
            Err(e) =>
            {
                return ScramResultServer::Error(e);
            }
        }
    }
}

impl<'ss, S, A, B> SyncScramServer<S, A, B>
where 
    S: ScramHashing + 'ss, A: ScramAuthServer<S> +'ss, B: ScramCbHelper + 'ss
{
    /// Consumes the instance converting it into the [ScramServerDyn]. So this instance
    /// can be stored in the vector.
    /// 
    /// The arg `id` allows to identify the instance. For this purposes the [PartialEq]
    /// [Eq] [Ord] [PartialOrd] were implemented for dyn instance.
    /// 
    /// But, in this case it is required to initialize the instance with the consumed 
    /// instances, otherwise it will be limited by 'ss lifetime.
    pub 
    fn make_dyn(mut self, id: NonZeroUsize) -> Box<dyn ScramServerDyn + 'ss>
    {
        self.id = id.get();

        return Box::new(self);
    }
}

impl<S: ScramHashing, A: ScramAuthServer<S>, B: ScramCbHelper> SyncScramServer<S, A, B>
{
    /// Creates a new instance of the SyncScramServer.
    /// 
    /// # Arguments
    /// 
    /// * `scram_auth_serv` - A reference to the instance which implements
    /// [ScramAuthServer]
    /// 
    /// * `data_chanbind` - A channel binding data TLS Endpoint Cert Hash
    /// 
    /// * `chan_bind_helper` - An implemented trait [ScramCbHelper] which should provide 
    ///                 crate with all necessary data for channel bind.
    /// 
    /// * `scram_nonce` - A [ScramNonce] Scram Nonce type
    /// 
    /// * `st` - [ScramType] A type of the scram picked by name from table [super::scram_common::SCRAM_TYPES]
    /// 
    /// * `ignore_exts` - if set to `true` ignores the additional data which may appear after the 
    ///         protocol data (i.e extensions which are not supported). If set to false generates
    ///         an error if any unknown data presents after expected data.
    /// 
    /// # Examples
    /// 
    /// ```ignore
    /// let serv = AuthServer::new();
    /// let nonce = ScramNonce::Base64(server_nonce);
    ///
    /// let scramtype = ScramCommon::get_scramtype("SCRAM-SHA-256").unwrap();
    /// let scram_res = ScramServer::<ScramSha256, AuthServer, AuthServer>::new(&serv, &serv, nonce, scramtype, false);
    /// ```
    pub 
    fn new(
        scram_auth_serv: A,
        chan_bind_helper: B,
        scram_nonce: ScramNonce, 
        st: &'static ScramType,
        ignore_exts: bool,
    ) -> ScramResult<SyncScramServer<S, A, B>>
    {

        let res = 
            Self
            {
                id: 0,
                hasher: PhantomData,
                auth: scram_auth_serv,
                st: st,
                username: None,
                authzid: None,
                sp: ScramPassword::default(),
                server_nonce: scram_nonce.get_nonce(),
                state: ScramState::WaitForClientInitalMsg,
                cli_chanbind: ChannelBindType::n(),
                chanbind_helper: chan_bind_helper,
                ignore_exts: ignore_exts
            };

        return Ok(res);
    }
 
    fn parse_response_internal(&mut self, resp: &str) -> ScramResult<String>
    {
        let parsed_resp = ScramData::try_from((resp, &self.state, self.ignore_exts))?;

        match parsed_resp
        {
            // Initial message from client
            ScramData::CmsgInitial{chan_bind, user, authzid, nonce} => //todo
            {
                //channel bind test
                chan_bind.server_initial_verify_client_cb(&self.st)?;

                //authID is not supported

                //get user
                let sp = 
                    self.auth.get_password_for_user(user, authzid)?;

                // form output
                let output = 
                    [
                        "r=", nonce, &self.server_nonce, 
                        ",s=", sp.get_salt_base64(), 
                        ",i=", &sp.get_iterations().to_string()
                    ].concat();

                // update state
                self.cli_chanbind = chan_bind;
                self.username = Some(user.to_string());
                self.authzid = authzid.map(|v| v.to_string());
                self.sp = sp;
                self.state = ScramState::WaitForClientFinalMsg{client_nonce: String::from(nonce)};

                return Ok(output);
            },
            ScramData::SmsgInitial{..} => 
            {
                panic!("server can not handle state: {}", parsed_resp);
            },
            //final message from client (STEP 2)
            ScramData::CmsgFinalMessage{chanbinding, finalnonce, proof, client_nonce} =>
            {
                // verify channel bind
                self.cli_chanbind
                    .server_final_verify_client_cb(
                        &self.st, 
                        chanbinding,
                        &self.chanbind_helper
                    )?;

                let nonce = 
                    [client_nonce, self.server_nonce.as_str()].concat();

                // verify nonce
                if finalnonce != nonce.as_str()
                {
                    scram_error!(
                        ScramErrorCode::VerificationError,
                        ScramServerError::OtherError,
                        "received invalid nonce username: '{}' rcvn: '{}' have: '{}'", 
                        self.username.as_ref().map_or("-missing-", |v| v.as_str()), finalnonce, client_nonce
                    );
                }
    
                //base64 config
                
                let client_final_without_proof = 
                    ["c=", chanbinding, ",r=", &nonce].concat();

                let client_first_bare = 
                    ["n=", self.username.as_ref().unwrap(), ",r=", client_nonce].concat();

                let server_first = 
                    [
                        "r=", &nonce, 
                        ",s=", self.sp.get_salt_base64(),
                        ",i=", &self.sp.get_iterations().to_string(),
                    ].concat();

                let authmsg = 
                    [
                        &client_first_bare,
                        ",",
                        &server_first,
                        ",",
                        &client_final_without_proof,
                    ].concat();
                
                let keys = self.sp.get_scram_keys();
                //b"Client Key"
                //b"Server Key"
                
                let client_key = S::hmac(keys.get_clinet_key(), &self.sp.get_salted_hashed_password())?;
                let server_key = S::hmac(keys.get_server_key(), &self.sp.get_salted_hashed_password())?;
                let stored_key = S::hash(&client_key);
                let client_signature = S::hmac(authmsg.as_bytes(), &stored_key)?;
                let server_signature = S::hmac(authmsg.as_bytes(), &server_key)?;

                let calc_client_proof = S::xor_arrays(&client_key, &client_signature)?;

                // verify proof
                let recv_decoded_proof = 
                    general_purpose::STANDARD.decode(proof)
                        .map_err(|e| 
                            scram_error_map!(
                                ScramErrorCode::MalformedScramMsg, 
                                ScramServerError::InvalidEncoding,
                                "base64 decode client proof failed, {}", e
                            )
                        )?;

                if calc_client_proof != recv_decoded_proof
                {
                    scram_error!(ScramErrorCode::VerificationError, ScramServerError::InvalidProof,
                        "received invalid proof from client, username: '{}'", 
                        self.username.as_ref().map_or("-missing-", |v| v.as_str()));
                }

                let output = ["v=", &general_purpose::STANDARD.encode(&server_signature)].concat();

                // update states
                self.state = ScramState::Completed;

                return Ok(output);
            },
            ScramData::SmsgFinalMessage{..} =>
            {
                panic!("server can not handle state: {}", parsed_resp);
            }
        }
    }
}




