use std::error::Error;
use std::convert::AsRef;
use strum_macros::AsRefStr;

use thiserror::Error as ThisError;

use aws_sdk_sts::{
    types::SdkError as AwsSdkError, 
    error::GetSessionTokenError,
};
use sqlx::{
    error::Error as SqlxError,
    migrate::MigrateError,
};

use serde::{Serialize, Serializer, ser::SerializeMap};


// pub struct SerializeError<E> {
//     pub err: E,
// }

// impl<E: std::error::Error> Serialize for SerializeError<E>
// {
//     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
//         let mut map = serializer.serialize_map(None)?;
//         map.serialize_entry("msg", &format!("{}", self.err))?;
//         if let Some(src) = self.err.source() {
//             let ser_src = SerializeError { err: src };
//             map.serialize_entry("source", &ser_src)?;
//         }
//         map.end()
//     }
// }

// impl<E: std::error::Error> From<E> for SerializeError<E> {
//     fn from(err: E) -> Self {
//         SerializeError { err }
//     }
// }


fn serialize_basic_err<E, S>(err: &E, serializer: S) -> Result<S::Ok, S::Error>
where
    E: std::error::Error + AsRef<str>,
    S: Serializer,
{
    let mut map = serializer.serialize_map(None)?;
    map.serialize_entry("code", err.as_ref())?;
    map.serialize_entry("msg", &format!("{err}"))?;
    if let Some(src) = err.source() {
        map.serialize_entry("source", &format!("{src}"))?;
    }
    map.end()
}


fn serialize_upstream_err<E, M>(err: &E, map: &mut M) -> Result<(), M::Error> 
where
    E: Error,
    M: serde::ser::SerializeMap,
{
    let src = err.source().map(|s| format!("{s}"));
    map.serialize_entry("source", &src)
}


macro_rules! impl_serialize_basic {
    ($err_type:ident) => {
        impl Serialize for $err_type {
            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
                serialize_basic_err(self, serializer)
            }
        }
    }
}


// error during initial setup (primarily loading state from db)
#[derive(Debug, ThisError, AsRefStr)]
pub enum SetupError {
    #[error("Invalid database record")]
    InvalidRecord, // e.g. wrong size blob for nonce or salt
    #[error("Error from database: {0}")]
    DbError(#[from] SqlxError),
    #[error("Error running migrations: {0}")]
    MigrationError(#[from] MigrateError),
    #[error("Error parsing configuration from database")]
    ConfigParseError(#[from] serde_json::Error),
}


// error when attempting to tell a request handler whether to release or deny credentials
#[derive(Debug, ThisError, AsRefStr)]
pub enum SendResponseError {
    #[error("The specified credentials request was not found")]
    NotFound, // no request with the given id
    #[error("The specified request was already closed by the client")]
    Abandoned, // request has already been closed by client
}


// errors encountered while handling an HTTP request
#[derive(Debug, ThisError, AsRefStr)]
pub enum RequestError {
    #[error("Error writing to stream: {0}")]
    StreamIOError(#[from] std::io::Error),
    // #[error("Received invalid UTF-8 in request")]
    // InvalidUtf8,
    // MalformedHttpRequest,
    #[error("HTTP request too large")]
    RequestTooLarge,
    #[error("Error accessing credentials: {0}")]
    NoCredentials(#[from] GetCredentialsError),
    #[error("Error getting client details: {0}")]
    ClientInfo(#[from] ClientInfoError),
    #[error("Error from Tauri: {0}")]
    Tauri(#[from] tauri::Error),
    #[error("No main application window found")]
    NoMainWindow,
}


#[derive(Debug, ThisError, AsRefStr)]
pub enum GetCredentialsError {
    #[error("Credentials are currently locked")]
    Locked,
    #[error("No credentials are known")]
    Empty,
}


#[derive(Debug, ThisError, AsRefStr)]
pub enum GetSessionError {
    #[error("Request completed successfully but no credentials were returned")]
    NoCredentials, // SDK returned successfully but credentials are None
    #[error("Error response from AWS SDK: {0}")]
    SdkError(#[from] AwsSdkError<GetSessionTokenError>),
}


#[derive(Debug, ThisError, AsRefStr)]
pub enum UnlockError {
    #[error("App is not locked")]
    NotLocked,
    #[error("No saved credentials were found")]
    NoCredentials,
    #[error("Invalid passphrase")]
    BadPassphrase,
    #[error("Data was found to be corrupt after decryption")]
    InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
    #[error("Database error: {0}")]
    DbError(#[from] SqlxError),
    #[error("Failed to create AWS session: {0}")]
    GetSession(#[from] GetSessionError),
}


// Errors encountered while trying to figure out who's on the other end of a request
#[derive(Debug, ThisError, AsRefStr)]
pub enum ClientInfoError {
    #[error("Found PID for client socket, but no corresponding process")]
    ProcessNotFound,
    #[error("Couldn't get client socket details: {0}")]
    NetstatError(#[from] netstat2::error::Error),
}


// =========================
// Serialize implementations
// =========================


struct SerializeWrapper<E>(pub E);

impl Serialize for SerializeWrapper<&GetSessionTokenError> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let err = self.0;
        let mut map = serializer.serialize_map(None)?;
        map.serialize_entry("code", &err.code())?;
        map.serialize_entry("msg", &err.message())?;
        map.serialize_entry("source", &None::<&str>)?;
        map.end()
    }
}



impl_serialize_basic!(SetupError);
impl_serialize_basic!(SendResponseError);
impl_serialize_basic!(GetCredentialsError);
impl_serialize_basic!(ClientInfoError);


impl Serialize for RequestError {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(None)?;
        map.serialize_entry("code", self.as_ref())?;
        map.serialize_entry("msg", &format!("{self}"))?;

        match self {
            RequestError::NoCredentials(src) => map.serialize_entry("source", &src)?,
            RequestError::ClientInfo(src) => map.serialize_entry("source", &src)?,
            _ => serialize_upstream_err(self, &mut map)?,
        }

        map.end()
    }
}


impl Serialize for GetSessionError {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(None)?;
        map.serialize_entry("code", self.as_ref())?;
        map.serialize_entry("msg", &format!("{self}"))?;

        match self {
            GetSessionError::SdkError(AwsSdkError::ServiceError(se_wrapper)) => {
                let err = se_wrapper.err();
                map.serialize_entry("source", &SerializeWrapper(err))?
            }
            _ => serialize_upstream_err(self, &mut map)?,
        }

        map.end()
    }
}


impl Serialize for UnlockError {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(None)?;
        map.serialize_entry("code", self.as_ref())?;
        map.serialize_entry("msg", &format!("{self}"))?;

        match self {
            UnlockError::GetSession(src) => map.serialize_entry("source", &src)?,
            _ => serialize_upstream_err(self, &mut map)?,
        }
        map.end()
    }
}
