// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

//! DLF Request Signer implementations for Alibaba Cloud Data Lake Formation.
//!
//! This module provides two signature algorithms for authenticating requests
//! to Alibaba Cloud Data Lake Formation (DLF) service:
//!
//! # Signature Algorithms
//!
//! ## 1. DLF4-HMAC-SHA256 (Default Signer)
//!
//! Used for VPC endpoints (e.g., `*-vpc.dlf.aliyuncs.com`).
//!
//! **Algorithm Overview:**
//! 1. Build a canonical request string from HTTP method, path, query params, and headers
//! 2. Create a string-to-sign with algorithm, timestamp, credential scope, and hashed canonical request
//! 3. Derive a signing key through multiple HMAC-SHA256 operations
//! 4. Calculate the signature and construct the Authorization header
//!
//! **Signing Key Derivation:**
//! ```text
//! kSecret = "aliyun_v4" + AccessKeySecret
//! kDate = HMAC-SHA256(kSecret, Date)
//! kRegion = HMAC-SHA256(kDate, Region)
//! kService = HMAC-SHA256(kRegion, "DlfNext")
//! kSigning = HMAC-SHA256(kService, "aliyun_v4_request")
//! ```
//!
//! **Authorization Header Format:**
//! ```text
//! DLF4-HMAC-SHA256 Credential=AccessKeyId/Date/Region/DlfNext/aliyun_v4_request,Signature=hex_signature
//! ```
//!
//! ## 2. HMAC-SHA1 (OpenAPI Signer)
//!
//! Used for public network endpoints (e.g., `dlfnext.*.aliyuncs.com`).
//! Follows Alibaba Cloud ROA v2 signature style.
//!
//! **Algorithm Overview:**
//! 1. Build canonicalized headers from x-acs-* headers
//! 2. Build canonicalized resource from path and query params
//! 3. Create string-to-sign with method, headers, and resource
//! 4. Calculate signature using HMAC-SHA1
//!
//! **Authorization Header Format:**
//! ```text
//! acs AccessKeyId:base64_signature
//! ```
//!
//! # References
//!
//! - [Alibaba Cloud API Signature](https://help.aliyun.com/document_detail/315526.html)
//! - [DLF OpenAPI](https://help.aliyun.com/document_detail/197826.html)
//!
//! # Usage
//!
//! The signer is automatically selected based on the endpoint URI:
//! - VPC endpoints → `DLFDefaultSigner` (DLF4-HMAC-SHA256)
//! - Public endpoints with "dlfnext" or "openapi" → `DLFOpenApiSigner` (HMAC-SHA1)

use std::collections::HashMap;

use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use chrono::{DateTime, Utc};
use hmac::{Hmac, Mac};
use md5::Md5;
use sha1::Sha1;
use sha2::{Digest, Sha256};
use uuid::Uuid;

use super::base::RESTAuthParameter;
use super::dlf_provider::DLFToken;

type HmacSha256 = Hmac<Sha256>;
type HmacSha1 = Hmac<Sha1>;

/// Trait for DLF request signers.
///
/// Different signers implement different signature algorithms for
/// authenticating requests to Alibaba Cloud DLF service.
///
/// # Implementations
///
/// - [`DLFDefaultSigner`]: Uses DLF4-HMAC-SHA256 for VPC endpoints
/// - [`DLFOpenApiSigner`]: Uses HMAC-SHA1 for public endpoints
pub trait DLFRequestSigner: Send + Sync {
    /// Generate signature headers for the request.
    fn sign_headers(
        &self,
        body: Option<&str>,
        now: &DateTime<Utc>,
        security_token: Option<&str>,
        host: &str,
    ) -> HashMap<String, String>;

    /// Generate the Authorization header value.
    fn authorization(
        &self,
        rest_auth_parameter: &RESTAuthParameter,
        token: &DLFToken,
        host: &str,
        sign_headers: &HashMap<String, String>,
    ) -> String;
    #[allow(dead_code)]
    /// Get the identifier for this signer.
    fn identifier(&self) -> &str;
}

/// Default DLF signer using DLF4-HMAC-SHA256 algorithm.
///
/// This signer is used for VPC endpoints (e.g., `cn-hangzhou-vpc.dlf.aliyuncs.com`).
///
/// # Algorithm Details
///
/// The DLF4-HMAC-SHA256 algorithm is similar to AWS Signature Version 4:
///
/// 1. **Canonical Request**: Combine HTTP method, URI, query string, headers, and payload hash
/// 2. **String-to-Sign**: Include algorithm, timestamp, credential scope, and canonical request hash
/// 3. **Signing Key**: Derived through chained HMAC operations
/// 4. **Signature**: HMAC-SHA256 of string-to-sign using the signing key
///
/// # Required Headers
///
/// The following headers are included in the signature calculation:
/// - `content-md5`: MD5 hash of request body (if present)
/// - `content-type`: Media type (if body present)
/// - `x-dlf-content-sha256`: Always "UNSIGNED-PAYLOAD"
/// - `x-dlf-date`: Request timestamp in format `%Y%m%dT%H%M%SZ`
/// - `x-dlf-version`: API version ("v1")
/// - `x-dlf-security-token`: Security token for temporary credentials (optional)
///
/// # Example
///
/// ```ignore
/// use paimon::api::auth::{DLFDefaultSigner, DLFRequestSigner};
///
/// let signer = DLFDefaultSigner::new("cn-hangzhou");
/// let headers = signer.sign_headers(Some(r#"{"key":"value"}"#), &Utc::now(), None, "dlf.aliyuncs.com");
/// ```
pub struct DLFDefaultSigner {
    region: String,
}

impl DLFDefaultSigner {
    pub const IDENTIFIER: &'static str = "default";
    const VERSION: &'static str = "v1";
    const SIGNATURE_ALGORITHM: &'static str = "DLF4-HMAC-SHA256";
    const PRODUCT: &'static str = "DlfNext";
    const REQUEST_TYPE: &'static str = "aliyun_v4_request";
    const SIGNATURE_KEY: &'static str = "Signature";
    const NEW_LINE: &'static str = "\n";

    // Header keys
    const DLF_CONTENT_MD5_HEADER_KEY: &'static str = "Content-MD5";
    const DLF_CONTENT_TYPE_KEY: &'static str = "Content-Type";
    const DLF_DATE_HEADER_KEY: &'static str = "x-dlf-date";
    const DLF_SECURITY_TOKEN_HEADER_KEY: &'static str = "x-dlf-security-token";
    const DLF_AUTH_VERSION_HEADER_KEY: &'static str = "x-dlf-version";
    const DLF_CONTENT_SHA256_HEADER_KEY: &'static str = "x-dlf-content-sha256";
    const DLF_CONTENT_SHA256_VALUE: &'static str = "UNSIGNED-PAYLOAD";

    const AUTH_DATE_TIME_FORMAT: &'static str = "%Y%m%dT%H%M%SZ";
    const MEDIA_TYPE: &'static str = "application/json";

    const SIGNED_HEADERS: &'static [&'static str] = &[
        "content-md5",
        "content-type",
        "x-dlf-content-sha256",
        "x-dlf-date",
        "x-dlf-version",
        "x-dlf-security-token",
    ];

    /// Create a new DLFDefaultSigner with the given region.
    pub fn new(region: impl Into<String>) -> Self {
        Self {
            region: region.into(),
        }
    }

    fn md5_base64(raw: &str) -> String {
        let mut hasher = Md5::new();
        hasher.update(raw.as_bytes());
        let hash = hasher.finalize();
        BASE64_STANDARD.encode(hash)
    }

    fn hmac_sha256(key: &[u8], data: &str) -> Vec<u8> {
        let mut mac = HmacSha256::new_from_slice(key).expect("HMAC can take key of any size");
        mac.update(data.as_bytes());
        mac.finalize().into_bytes().to_vec()
    }

    fn sha256_hex(raw: &str) -> String {
        let mut hasher = Sha256::new();
        hasher.update(raw.as_bytes());
        hex::encode(hasher.finalize())
    }

    fn hex_encode(raw: &[u8]) -> String {
        hex::encode(raw)
    }

    fn trim(value: &str) -> &str {
        value.trim()
    }

    fn get_canonical_request(
        &self,
        rest_auth_parameter: &RESTAuthParameter,
        headers: &HashMap<String, String>,
    ) -> String {
        let mut parts = vec![
            rest_auth_parameter.method.clone(),
            rest_auth_parameter.path.clone(),
        ];

        let canonical_query_string =
            self.build_canonical_query_string(&rest_auth_parameter.parameters);
        parts.push(canonical_query_string);

        let sorted_headers = self.build_sorted_signed_headers_map(headers);
        for (key, value) in sorted_headers {
            parts.push(format!("{key}:{value}"));
        }

        let content_sha256 = headers
            .get(Self::DLF_CONTENT_SHA256_HEADER_KEY)
            .map(|s| s.as_str())
            .unwrap_or(Self::DLF_CONTENT_SHA256_VALUE);
        parts.push(content_sha256.to_string());

        parts.join(Self::NEW_LINE)
    }

    fn build_canonical_query_string(&self, parameters: &HashMap<String, String>) -> String {
        if parameters.is_empty() {
            return String::new();
        }

        let mut sorted_params: Vec<_> = parameters.iter().collect();
        sorted_params.sort_by(|a, b| a.0.cmp(b.0));

        let query_parts: Vec<String> = sorted_params
            .iter()
            .map(|(key, value)| {
                let key = Self::trim(key);
                if !value.is_empty() {
                    let value = Self::trim(value);
                    format!("{key}={value}")
                } else {
                    key.to_string()
                }
            })
            .collect();

        query_parts.join("&")
    }

    fn build_sorted_signed_headers_map(
        &self,
        headers: &HashMap<String, String>,
    ) -> Vec<(String, String)> {
        let mut sorted_headers: Vec<(String, String)> = headers
            .iter()
            .filter(|(key, _)| {
                let lower_key = key.to_lowercase();
                Self::SIGNED_HEADERS.contains(&lower_key.as_str())
            })
            .map(|(key, value)| (key.to_lowercase(), Self::trim(value).to_string()))
            .collect();

        sorted_headers.sort_by(|a, b| a.0.cmp(&b.0));
        sorted_headers
    }
}

impl DLFRequestSigner for DLFDefaultSigner {
    fn sign_headers(
        &self,
        body: Option<&str>,
        now: &DateTime<Utc>,
        security_token: Option<&str>,
        _host: &str,
    ) -> HashMap<String, String> {
        let mut sign_headers = HashMap::new();

        let date_time = now.format(Self::AUTH_DATE_TIME_FORMAT).to_string();
        sign_headers.insert(Self::DLF_DATE_HEADER_KEY.to_string(), date_time);
        sign_headers.insert(
            Self::DLF_CONTENT_SHA256_HEADER_KEY.to_string(),
            Self::DLF_CONTENT_SHA256_VALUE.to_string(),
        );
        sign_headers.insert(
            Self::DLF_AUTH_VERSION_HEADER_KEY.to_string(),
            Self::VERSION.to_string(),
        );

        if let Some(body_content) = body {
            if !body_content.is_empty() {
                sign_headers.insert(
                    Self::DLF_CONTENT_TYPE_KEY.to_string(),
                    Self::MEDIA_TYPE.to_string(),
                );
                sign_headers.insert(
                    Self::DLF_CONTENT_MD5_HEADER_KEY.to_string(),
                    Self::md5_base64(body_content),
                );
            }
        }

        if let Some(token) = security_token {
            sign_headers.insert(
                Self::DLF_SECURITY_TOKEN_HEADER_KEY.to_string(),
                token.to_string(),
            );
        }

        sign_headers
    }

    fn authorization(
        &self,
        rest_auth_parameter: &RESTAuthParameter,
        token: &DLFToken,
        _host: &str,
        sign_headers: &HashMap<String, String>,
    ) -> String {
        let date_time = sign_headers.get(Self::DLF_DATE_HEADER_KEY).unwrap();
        let date = &date_time[..8];

        let canonical_request = self.get_canonical_request(rest_auth_parameter, sign_headers);

        let string_to_sign = [
            Self::SIGNATURE_ALGORITHM.to_string(),
            date_time.clone(),
            format!(
                "{}/{}/{}/{}",
                date,
                self.region,
                Self::PRODUCT,
                Self::REQUEST_TYPE
            ),
            Self::sha256_hex(&canonical_request),
        ]
        .join(Self::NEW_LINE);

        // Derive signing key
        let date_key = Self::hmac_sha256(
            format!("aliyun_v4{}", token.access_key_secret).as_bytes(),
            date,
        );
        let date_region_key = Self::hmac_sha256(&date_key, &self.region);
        let date_region_service_key = Self::hmac_sha256(&date_region_key, Self::PRODUCT);
        let signing_key = Self::hmac_sha256(&date_region_service_key, Self::REQUEST_TYPE);

        let signature_bytes = Self::hmac_sha256(&signing_key, &string_to_sign);
        let signature = Self::hex_encode(&signature_bytes);

        format!(
            "{} Credential={}/{}/{}/{}/{},{}={}",
            Self::SIGNATURE_ALGORITHM,
            token.access_key_id,
            date,
            self.region,
            Self::PRODUCT,
            Self::REQUEST_TYPE,
            Self::SIGNATURE_KEY,
            signature
        )
    }

    fn identifier(&self) -> &str {
        Self::IDENTIFIER
    }
}

/// DLF OpenAPI signer using HMAC-SHA1 algorithm.
///
/// This signer follows the Alibaba Cloud ROA v2 signature style and is used
/// for public network endpoints (e.g., `dlfnext.cn-asdnbhwf.aliyuncs.com`).
///
/// # Algorithm Details
///
/// The HMAC-SHA1 algorithm is the traditional Alibaba Cloud API signature method:
///
/// 1. **Canonicalized Headers**: Sort and format all `x-acs-*` headers
/// 2. **Canonicalized Resource**: URL-decoded path with sorted query parameters
/// 3. **String-to-Sign**: Combine HTTP method, standard headers, canonicalized headers, and resource
/// 4. **Signature**: Base64-encoded HMAC-SHA1 of string-to-sign
///
/// # Required Headers
///
/// The following headers are included in requests:
/// - `Date`: Request timestamp in RFC 1123 format
/// - `Accept`: Always "application/json"
/// - `Content-MD5`: MD5 hash of request body (if present)
/// - `Content-Type`: Always "application/json" (if body present)
/// - `Host`: Endpoint host
/// - `x-acs-signature-method`: Always "HMAC-SHA1"
/// - `x-acs-signature-nonce`: Unique UUID for each request
/// - `x-acs-signature-version`: Always "1.0"
/// - `x-acs-version`: API version ("2026-01-18")
/// - `x-acs-security-token`: Security token for temporary credentials (optional)
///
/// # Example
///
/// ```ignore
/// use paimon::api::auth::{DLFOpenApiSigner, DLFRequestSigner};
///
/// let signer = DLFOpenApiSigner;
/// let headers = signer.sign_headers(Some(r#"{"key":"value"}"#), &Utc::now(), None, "dlfnext.aliyuncs.com");
/// ```
pub struct DLFOpenApiSigner;

impl DLFOpenApiSigner {
    pub const IDENTIFIER: &'static str = "openapi";

    // Header constants
    const DATE_HEADER: &'static str = "Date";
    const ACCEPT_HEADER: &'static str = "Accept";
    const CONTENT_MD5_HEADER: &'static str = "Content-MD5";
    const CONTENT_TYPE_HEADER: &'static str = "Content-Type";
    const HOST_HEADER: &'static str = "Host";
    const X_ACS_SIGNATURE_METHOD: &'static str = "x-acs-signature-method";
    const X_ACS_SIGNATURE_NONCE: &'static str = "x-acs-signature-nonce";
    const X_ACS_SIGNATURE_VERSION: &'static str = "x-acs-signature-version";
    const X_ACS_VERSION: &'static str = "x-acs-version";
    const X_ACS_SECURITY_TOKEN: &'static str = "x-acs-security-token";

    // Values
    const DATE_FORMAT: &'static str = "%a, %d %b %Y %H:%M:%S GMT";
    const ACCEPT_VALUE: &'static str = "application/json";
    const CONTENT_TYPE_VALUE: &'static str = "application/json";
    const SIGNATURE_METHOD_VALUE: &'static str = "HMAC-SHA1";
    const SIGNATURE_VERSION_VALUE: &'static str = "1.0";
    const API_VERSION: &'static str = "2026-01-18";

    fn md5_base64(data: &str) -> String {
        let mut hasher = Md5::new();
        hasher.update(data.as_bytes());
        let hash = hasher.finalize();
        BASE64_STANDARD.encode(hash)
    }

    fn hmac_sha1_base64(key: &str, data: &str) -> String {
        let mut mac =
            HmacSha1::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
        mac.update(data.as_bytes());
        BASE64_STANDARD.encode(mac.finalize().into_bytes())
    }

    fn trim(value: &str) -> &str {
        value.trim()
    }

    fn build_canonicalized_headers(&self, headers: &HashMap<String, String>) -> String {
        let mut sorted_headers: Vec<(String, String)> = headers
            .iter()
            .filter(|(key, _)| key.to_lowercase().starts_with("x-acs-"))
            .map(|(key, value)| (key.to_lowercase(), Self::trim(value).to_string()))
            .collect();

        sorted_headers.sort_by(|a, b| a.0.cmp(&b.0));

        let mut result = String::new();
        for (key, value) in sorted_headers {
            result.push_str(&format!("{key}:{value}\n"));
        }
        result
    }

    fn build_canonicalized_resource(&self, rest_auth_parameter: &RESTAuthParameter) -> String {
        let path = urlencoding::decode(&rest_auth_parameter.path).unwrap_or_default();

        if rest_auth_parameter.parameters.is_empty() {
            return path.to_string();
        }

        let mut sorted_params: Vec<_> = rest_auth_parameter.parameters.iter().collect();
        sorted_params.sort_by(|a, b| a.0.cmp(b.0));

        let query_parts: Vec<String> = sorted_params
            .iter()
            .map(|(key, value)| {
                let decoded_value = urlencoding::decode(value).unwrap_or_default();
                if !decoded_value.is_empty() {
                    format!("{key}={decoded_value}")
                } else {
                    key.to_string()
                }
            })
            .collect();

        format!("{}?{}", path, query_parts.join("&"))
    }

    fn build_string_to_sign(
        &self,
        rest_auth_parameter: &RESTAuthParameter,
        headers: &HashMap<String, String>,
        canonicalized_headers: &str,
        canonicalized_resource: &str,
    ) -> String {
        let parts = [
            rest_auth_parameter.method.clone(),
            headers
                .get(Self::ACCEPT_HEADER)
                .cloned()
                .unwrap_or_default(),
            headers
                .get(Self::CONTENT_MD5_HEADER)
                .cloned()
                .unwrap_or_default(),
            headers
                .get(Self::CONTENT_TYPE_HEADER)
                .cloned()
                .unwrap_or_default(),
            headers.get(Self::DATE_HEADER).cloned().unwrap_or_default(),
            canonicalized_headers.to_string(),
        ];

        parts.join("\n") + canonicalized_resource
    }
}

impl DLFRequestSigner for DLFOpenApiSigner {
    fn sign_headers(
        &self,
        body: Option<&str>,
        now: &DateTime<Utc>,
        security_token: Option<&str>,
        host: &str,
    ) -> HashMap<String, String> {
        let mut headers = HashMap::new();

        // Date header in RFC 1123 format
        headers.insert(
            Self::DATE_HEADER.to_string(),
            now.format(Self::DATE_FORMAT).to_string(),
        );

        // Accept header
        headers.insert(
            Self::ACCEPT_HEADER.to_string(),
            Self::ACCEPT_VALUE.to_string(),
        );

        // Content-MD5 and Content-Type (if body exists)
        if let Some(body_content) = body {
            if !body_content.is_empty() {
                headers.insert(
                    Self::CONTENT_MD5_HEADER.to_string(),
                    Self::md5_base64(body_content),
                );
                headers.insert(
                    Self::CONTENT_TYPE_HEADER.to_string(),
                    Self::CONTENT_TYPE_VALUE.to_string(),
                );
            }
        }

        // Host header
        headers.insert(Self::HOST_HEADER.to_string(), host.to_string());

        // x-acs-* headers
        headers.insert(
            Self::X_ACS_SIGNATURE_METHOD.to_string(),
            Self::SIGNATURE_METHOD_VALUE.to_string(),
        );
        headers.insert(
            Self::X_ACS_SIGNATURE_NONCE.to_string(),
            Uuid::new_v4().to_string(),
        );
        headers.insert(
            Self::X_ACS_SIGNATURE_VERSION.to_string(),
            Self::SIGNATURE_VERSION_VALUE.to_string(),
        );
        headers.insert(
            Self::X_ACS_VERSION.to_string(),
            Self::API_VERSION.to_string(),
        );

        // Security token (if present)
        if let Some(token) = security_token {
            headers.insert(Self::X_ACS_SECURITY_TOKEN.to_string(), token.to_string());
        }

        headers
    }

    fn authorization(
        &self,
        rest_auth_parameter: &RESTAuthParameter,
        token: &DLFToken,
        _host: &str,
        sign_headers: &HashMap<String, String>,
    ) -> String {
        let canonicalized_headers = self.build_canonicalized_headers(sign_headers);
        let canonicalized_resource = self.build_canonicalized_resource(rest_auth_parameter);
        let string_to_sign = self.build_string_to_sign(
            rest_auth_parameter,
            sign_headers,
            &canonicalized_headers,
            &canonicalized_resource,
        );

        let signature = Self::hmac_sha1_base64(&token.access_key_secret, &string_to_sign);
        format!("acs {}:{}", token.access_key_id, signature)
    }

    fn identifier(&self) -> &str {
        Self::IDENTIFIER
    }
}

/// use paimon::api::auth::DLFSignerFactory;
///
/// // Auto-detect from URI
/// let signer = DLFSignerFactory::create_signer("default", "cn-hangzhou");
/// let algo = DLFSignerFactory::parse_signing_algo_from_uri(Some("http://dlfnext.ajinnbjug.aliyuncs.com"));
/// assert_eq!(algo, "openapi");
/// ```
pub struct DLFSignerFactory;

impl DLFSignerFactory {
    /// Create a signer based on the signing algorithm.
    pub fn create_signer(signing_algorithm: &str, region: &str) -> Box<dyn DLFRequestSigner> {
        if signing_algorithm == DLFOpenApiSigner::IDENTIFIER {
            Box::new(DLFOpenApiSigner)
        } else {
            Box::new(DLFDefaultSigner::new(region))
        }
    }
}
