use http::header;
use http::HeaderMap;
use http::HeaderValue;
use tracing::Span;

use super::auth_result::map_auth_result_with_message;
use super::auth_result::AuthResult;
use super::configuration::RuleConfiguration;
use super::token::bearer::header::BearerAuthorizationHeader;
use super::token::hmac::check::HMACChecker;
use super::token::hmac::header::HMACAuthorizationHeader;
use super::token::jwt::check::JWTChecker;

pub fn check_auth<H: HMACChecker, J: JWTChecker>(
    configuration: &RuleConfiguration,
    headers: &HeaderMap<HeaderValue>,
    hmac_checker: &H,
    jwt_checker: &J,
) -> AuthResult {
    let mut auth_iter = headers.get_all(header::AUTHORIZATION).iter();

    let Some(auth) = auth_iter.next() else {
        return map_auth_result_with_message(
            configuration.missing_auth,
            "Missing Authorization header.",
        );
    };

    let None = auth_iter.next() else {
        return map_auth_result_with_message(
            configuration.multiple_auth,
            "Multiple Authorization headers provided.",
        );
    };

    let Ok(auth_str) = auth.to_str() else {
        return map_auth_result_with_message(
            configuration.malformed_auth,
            "Malformed Authorization header provided.",
        );
    };

    if let Ok(bearer) = BearerAuthorizationHeader::try_from(auth_str) {
        Span::current().record("auth.method", "JWT");
        return jwt_checker.check_jwt_auth(&configuration.jwt, bearer.0);
    }

    if let Ok(hmac) = HMACAuthorizationHeader::try_from(auth_str) {
        Span::current().record("auth.method", "HMAC");
        Span::current().record("hmac.sender", hmac.sender);
        Span::current().record("hmac.recipient", hmac.recipient);
        return hmac_checker.check_hmac_auth(&configuration.hmac, hmac);
    }

    map_auth_result_with_message(
        configuration.unknown_auth,
        "Unknown Authorization provided.",
    )
}

#[cfg(test)]
mod test {
    use crate::plugins::auth_enforcement::token::hmac::check::mock::MockHMACChecker;
    use crate::plugins::auth_enforcement::token::jwt::check::mock::MockJWTChecker;

    use super::super::auth_result::AuthResultConfiguration;
    use http::header;

    use super::*;

    const INVALID_ASCII_SEQUENCE: [u8; 5] = [116, 101, 115, 116, 230];

    #[test]
    fn test_check_auth_no_auth_header_ok() {
        let mut config = RuleConfiguration::all_warning();
        config.missing_auth = AuthResultConfiguration::Ok;

        let headers = HeaderMap::new();

        let hmac_checker = MockHMACChecker::new(AuthResult::Warning {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(result, AuthResult::Ok);
    }

    #[test]
    fn test_check_auth_no_auth_header_warning() {
        let mut config = RuleConfiguration::all_block();
        config.missing_auth = AuthResultConfiguration::Warning;

        let headers = HeaderMap::new();

        let hmac_checker = MockHMACChecker::new(AuthResult::Block {
            message: "HMAC Block",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Warning {
                message: "Missing Authorization header."
            }
        );
    }

    #[test]
    fn test_check_auth_no_auth_header_block() {
        let mut config = RuleConfiguration::all_ok();
        config.missing_auth = AuthResultConfiguration::Block;

        let headers = HeaderMap::new();

        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Block {
                message: "Missing Authorization header."
            }
        );
    }

    #[test]
    fn test_check_auth_multiple_auth_header_ok() {
        let mut config = RuleConfiguration::all_warning();
        config.multiple_auth = AuthResultConfiguration::Ok;

        let headers = HeaderMap::from_iter([
            (header::AUTHORIZATION, HeaderValue::from_static("Auth 1")),
            (header::AUTHORIZATION, HeaderValue::from_static("Auth 2")),
        ]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Warning {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(result, AuthResult::Ok);
    }

    #[test]
    fn test_check_auth_multiple_auth_header_warning() {
        let mut config = RuleConfiguration::all_block();
        config.multiple_auth = AuthResultConfiguration::Warning;

        let headers = HeaderMap::from_iter([
            (header::AUTHORIZATION, HeaderValue::from_static("Auth 1")),
            (header::AUTHORIZATION, HeaderValue::from_static("Auth 2")),
        ]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Block {
            message: "HMAC Block",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Warning {
                message: "Multiple Authorization headers provided."
            }
        );
    }

    #[test]
    fn test_check_auth_multiple_auth_header_block() {
        let mut config = RuleConfiguration::all_ok();
        config.multiple_auth = AuthResultConfiguration::Block;

        let headers = HeaderMap::from_iter([
            (header::AUTHORIZATION, HeaderValue::from_static("Auth 1")),
            (header::AUTHORIZATION, HeaderValue::from_static("Auth 2")),
        ]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Block {
                message: "Multiple Authorization headers provided."
            }
        );
    }

    #[test]
    fn test_check_auth_malformed_header_ok() {
        let mut config = RuleConfiguration::all_warning();
        config.malformed_auth = AuthResultConfiguration::Ok;

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::try_from(INVALID_ASCII_SEQUENCE.to_vec()).unwrap(),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Warning {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(result, AuthResult::Ok);
    }

    #[test]
    fn test_check_auth_malformed_header_warning() {
        let mut config = RuleConfiguration::all_block();
        config.malformed_auth = AuthResultConfiguration::Warning;

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::try_from(INVALID_ASCII_SEQUENCE.to_vec()).unwrap(),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Block {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Warning {
                message: "Malformed Authorization header provided."
            }
        );
    }

    #[test]
    fn test_check_auth_malformed_header_block() {
        let mut config = RuleConfiguration::all_ok();
        config.malformed_auth = AuthResultConfiguration::Block;

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::try_from(INVALID_ASCII_SEQUENCE.to_vec()).unwrap(),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Block {
                message: "Malformed Authorization header provided."
            }
        );
    }

    #[test]
    fn test_check_auth_bearer_jwt_ok() {
        let config = RuleConfiguration::all_warning();

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("Bearer someToken"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Warning {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(result, AuthResult::Ok);
    }

    #[test]
    fn test_check_auth_bearer_jwt_warning() {
        let config = RuleConfiguration::all_warning();

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("Bearer someToken"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = MockJWTChecker::new(AuthResult::Warning {
            message: "Invalid JWT",
        });

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Warning {
                message: "Invalid JWT"
            }
        );
    }

    #[test]
    fn test_check_auth_bearer_jwt_block() {
        let config = RuleConfiguration::all_block();

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("Bearer someToken"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = MockJWTChecker::new(AuthResult::Block {
            message: "Expired JWT",
        });

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Block {
                message: "Expired JWT"
            }
        );
    }

    #[test]
    fn test_check_auth_bearer_strips_prefix_before_passing_to_jwt_checker() {
        use crate::plugins::auth_enforcement::token::jwt::configuration::JWTRuleConfiguration;
        use std::cell::RefCell;

        struct CapturingJWTChecker(RefCell<Option<String>>);
        impl JWTChecker for CapturingJWTChecker {
            fn check_jwt_auth(&self, _: &JWTRuleConfiguration, token: &str) -> AuthResult {
                *self.0.borrow_mut() = Some(token.to_string());
                AuthResult::Ok
            }
        }

        let config = RuleConfiguration::all_ok();
        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("Bearer actual.token.value"),
        )]);
        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = CapturingJWTChecker(RefCell::new(None));

        check_auth(&config, &headers, &hmac_checker, &jwt_checker);

        assert_eq!(
            jwt_checker.0.borrow().as_deref(),
            Some("actual.token.value")
        );
    }

    #[test]
    fn test_check_auth_hmac_ok() {
        let config = RuleConfiguration::all_warning();

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("sender/recipient:5745fd9c0640945e8b744def34ebafff9e9f6846"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(result, AuthResult::Ok);
    }

    #[test]
    fn test_check_auth_hmac_warning() {
        let config = RuleConfiguration::all_block();

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("sender/recipient:5745fd9c0640945e8b744def34ebafff9e9f6846"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Warning {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Warning {
                message: "HMAC Warning"
            }
        );
    }

    #[test]
    fn test_check_auth_hmac_block() {
        let config = RuleConfiguration::all_ok();

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("sender/recipient:5745fd9c0640945e8b744def34ebafff9e9f6846"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Block {
            message: "HMAC Block",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Block {
                message: "HMAC Block"
            }
        );
    }

    #[test]
    fn test_check_auth_unknown_auth_ok() {
        let mut config = RuleConfiguration::all_warning();
        config.unknown_auth = AuthResultConfiguration::Ok;

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("Not Real Auth"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Warning {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(result, AuthResult::Ok);
    }

    #[test]
    fn test_check_auth_unknown_auth_warning() {
        let mut config = RuleConfiguration::all_block();
        config.unknown_auth = AuthResultConfiguration::Warning;

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("Not Real Auth"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Block {
            message: "HMAC Block",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Warning {
                message: "Unknown Authorization provided."
            }
        );
    }

    #[test]
    fn test_check_auth_unknown_auth_block() {
        let mut config = RuleConfiguration::all_ok();
        config.unknown_auth = AuthResultConfiguration::Block;

        let headers = HeaderMap::from_iter([(
            header::AUTHORIZATION,
            HeaderValue::from_static("Not Real Auth"),
        )]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Ok);
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(
            result,
            AuthResult::Block {
                message: "Unknown Authorization provided."
            }
        );
    }

    #[test]
    fn test_check_auth_unknown_auth_ok_with_extra_headers() {
        let mut config = RuleConfiguration::all_warning();
        config.unknown_auth = AuthResultConfiguration::Ok;

        let headers = HeaderMap::from_iter([
            (
                header::AUTHORIZATION,
                HeaderValue::from_static("Not Real Auth"),
            ),
            (header::ACCEPT, HeaderValue::from_static("Another header")),
        ]);

        let hmac_checker = MockHMACChecker::new(AuthResult::Warning {
            message: "HMAC Warning",
        });
        let jwt_checker = MockJWTChecker::new(AuthResult::Ok);

        let result = check_auth(&config, &headers, &hmac_checker, &jwt_checker);
        assert_eq!(result, AuthResult::Ok);
    }
}
