// Copyright 2024 RustFS Team
//
// 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.

use std::error::Error as StdError;
use std::fmt::{Display, Formatter};
use std::io::{Error, ErrorKind};

pub const SIGNER_HEADER_ERROR_MARKER: &str = "rustfs_signer_header_error";

#[derive(Debug)]
struct SignerHeaderError {
    scope: String,
    header_name: String,
}

impl SignerHeaderError {
    fn new(scope: &str, header_name: &str) -> Self {
        Self {
            scope: scope.to_string(),
            header_name: header_name.to_string(),
        }
    }
}

impl Display for SignerHeaderError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}: invalid UTF-8 header value for `{}` [{}]",
            self.scope, self.header_name, SIGNER_HEADER_ERROR_MARKER
        )
    }
}

impl StdError for SignerHeaderError {}

pub fn invalid_utf8_header_error(scope: &str, header_name: &str) -> Error {
    Error::new(ErrorKind::InvalidInput, SignerHeaderError::new(scope, header_name))
}

pub fn signer_error_to_io_error(scope: &str, error: rustfs_signer::SignV4Error) -> Error {
    match error {
        rustfs_signer::SignV4Error::InvalidHeaderValue { name } => invalid_utf8_header_error(scope, &name),
        other => Error::other(format!("{scope}: {other}")),
    }
}

pub fn error_chain_contains_signer_header_marker(err: &(dyn StdError + 'static)) -> bool {
    let mut current = Some(err);
    while let Some(source) = current {
        if source.downcast_ref::<SignerHeaderError>().is_some() {
            return true;
        }

        if source.to_string().contains(SIGNER_HEADER_ERROR_MARKER) {
            return true;
        }

        current = source.source();
    }

    false
}

#[cfg(test)]
mod tests {
    use super::{error_chain_contains_signer_header_marker, invalid_utf8_header_error, signer_error_to_io_error};

    #[test]
    fn invalid_utf8_header_error_is_detected_through_error_chain() {
        let err = invalid_utf8_header_error("failed to sign request", "x-amz-meta-invalid");

        assert!(error_chain_contains_signer_header_marker(&err));
    }

    #[test]
    fn mapped_signer_header_error_is_detected_through_error_chain() {
        let err = signer_error_to_io_error(
            "failed to sign request",
            rustfs_signer::SignV4Error::InvalidHeaderValue {
                name: "x-amz-meta-invalid".to_string(),
            },
        );

        assert!(error_chain_contains_signer_header_marker(&err));
    }

    #[test]
    fn generic_io_errors_do_not_match_signer_header_marker() {
        let err = std::io::Error::other("unrelated failure");

        assert!(!error_chain_contains_signer_header_marker(&err));
    }
}
