//! Brand and sec-ch-ua generation logic.
//!
//! This module implements Chromium's algorithm for generating Client Hints
//! including the "greased" brand that varies by version.

use std::fmt;

/// Supported browser brands.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Brand {
    /// Google Chrome
    Chrome,
    /// Microsoft Edge
    Edge,
    /// Brave Browser
    Brave,
    /// Opera Browser
    Opera,
    /// Vivaldi Browser
    Vivaldi,
    /// Samsung Internet
    Samsung,
    /// Yandex Browser
    Yandex,
    /// Generic Chromium (when specific brand is unknown)
    Chromium,
}

impl Brand {
    /// Returns the brand name as used in sec-ch-ua headers.
    #[inline]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Brand::Chrome => "Google Chrome",
            Brand::Edge => "Microsoft Edge",
            Brand::Brave => "Brave",
            Brand::Opera => "Opera",
            Brand::Vivaldi => "Vivaldi",
            Brand::Samsung => "Samsung Internet",
            Brand::Yandex => "Yandex Browser",
            Brand::Chromium => "Chromium",
        }
    }
}

impl fmt::Display for Brand {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// Characters used for greased brand generation (Chromium >= 105).
/// These are intentionally "unusual" characters to test server robustness.
const GREASED_CHARS_V105: &[u8] = b" ()-./:;=?_";

/// Version strings used for greased brand version.
const GREASED_VERSIONS: [&str; 3] = ["8", "99", "24"];

/// Permutation orders for the three brand entries in sec-ch-ua.
/// Index: 0 = greased brand, 1 = chromium, 2 = brand
const PERMUTATIONS: [[usize; 3]; 6] = [
    [0, 1, 2], // Greased, Chromium, Brand
    [0, 2, 1], // Greased, Brand, Chromium
    [1, 0, 2], // Chromium, Greased, Brand
    [2, 0, 1], // Brand, Greased, Chromium
    [1, 2, 0], // Chromium, Brand, Greased
    [2, 1, 0], // Brand, Chromium, Greased
];

/// Format a brand-version pair for sec-ch-ua header.
/// Example: "Google Chrome";v="120"
#[inline]
fn format_brand(brand: &str, version: &str) -> String {
    format!("\"{}\";v=\"{}\"", brand, version)
}

/// Generate the greased brand string based on major version.
///
/// This implements Chromium's GREASE algorithm which creates a fake brand
/// that varies by version to prevent servers from depending on specific patterns.
fn generate_greased_brand(major_version: u32) -> String {
    if major_version < 105 {
        // Legacy greased brand generation
        if major_version == 103 {
            // Special case for version 103
            return ".Not/A)Brand".to_string();
        }
        // For versions <= 102 and version 104
        return " Not A;Brand".to_string();
    }

    // For version >= 105, use the new algorithm
    let offset = (major_version - 105) as usize;

    let (idx1, idx2) = if offset == 0 {
        // Special case for version 105: use indices 2 and 7
        (2, 7)
    } else {
        // For version >= 106: use consecutive indices starting from offset + 6
        ((offset + 6) % 11, (offset + 7) % 11)
    };

    let c1 = GREASED_CHARS_V105[idx1] as char;
    let c2 = GREASED_CHARS_V105[idx2] as char;

    format!("Not{}A{}Brand", c1, c2)
}

/// Get the greased version string based on major version.
#[inline]
fn get_greased_version(major_version: u32) -> &'static str {
    if major_version < 105 {
        // Legacy versions always use "99"
        return "99";
    }
    // For v >= 105, cycle through [8, 99, 24]
    let idx = (major_version as usize) % GREASED_VERSIONS.len();
    GREASED_VERSIONS[idx]
}

/// Get the permutation order based on major version.
#[inline]
fn get_permutation(major_version: u32) -> [usize; 3] {
    if major_version < 103 {
        // Legacy versions always use [Greased, Chromium, Brand]
        return PERMUTATIONS[0];
    }
    let idx = (major_version as usize) % PERMUTATIONS.len();
    PERMUTATIONS[idx]
}

/// Generate the complete sec-ch-ua header value.
///
/// This creates the header with three brand entries:
/// 1. A greased (fake) brand to prevent server fingerprinting
/// 2. The Chromium brand/version
/// 3. The actual browser brand/version
///
/// The order of these entries is determined by the major version.
pub fn generate_sec_ch_ua(brand: Brand, major_version: u32) -> String {
    let version_str = major_version.to_string();
    generate_sec_ch_ua_with_versions(brand, major_version, &version_str, &version_str)
}

/// Generate sec-ch-ua-full-version-list header value with full versions.
pub fn generate_sec_ch_ua_full_version(
    brand: Brand,
    major_version: u32,
    full_version: &str,
) -> String {
    generate_sec_ch_ua_with_versions(brand, major_version, full_version, full_version)
}

/// Internal function to generate sec-ch-ua with specified versions.
fn generate_sec_ch_ua_with_versions(
    brand: Brand,
    major_version: u32,
    chromium_version: &str,
    brand_version: &str,
) -> String {
    // Generate the three brand entries
    let greased_brand = generate_greased_brand(major_version);
    let greased_version = get_greased_version(major_version);

    let entries = [
        format_brand(&greased_brand, greased_version),
        format_brand("Chromium", chromium_version),
        format_brand(brand.as_str(), brand_version),
    ];

    // Apply permutation
    let perm = get_permutation(major_version);

    // Preallocate string with estimated capacity
    let mut result = String::with_capacity(128);

    result.push_str(&entries[perm[0]]);
    result.push_str(", ");
    result.push_str(&entries[perm[1]]);
    result.push_str(", ");
    result.push_str(&entries[perm[2]]);

    result
}

#[cfg(test)]
mod tests {
    use super::*;

    // Test cases matching the original Go implementation
    #[test]
    fn test_client_hint_ua_v80() {
        let result = generate_sec_ch_ua(Brand::Chrome, 80);
        assert_eq!(
            result,
            "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"80\", \"Google Chrome\";v=\"80\""
        );
    }

    #[test]
    fn test_client_hint_ua_v98() {
        let result = generate_sec_ch_ua(Brand::Chrome, 98);
        assert_eq!(
            result,
            "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\""
        );
    }

    #[test]
    fn test_client_hint_ua_v99() {
        let result = generate_sec_ch_ua(Brand::Chrome, 99);
        assert_eq!(
            result,
            "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"99\", \"Google Chrome\";v=\"99\""
        );
    }

    #[test]
    fn test_client_hint_ua_v100() {
        let result = generate_sec_ch_ua(Brand::Chrome, 100);
        assert_eq!(
            result,
            "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\""
        );
    }

    #[test]
    fn test_client_hint_ua_v101() {
        let result = generate_sec_ch_ua(Brand::Chrome, 101);
        assert_eq!(
            result,
            "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Google Chrome\";v=\"101\""
        );
    }

    #[test]
    fn test_client_hint_ua_v102() {
        let result = generate_sec_ch_ua(Brand::Chrome, 102);
        assert_eq!(
            result,
            "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"102\", \"Google Chrome\";v=\"102\""
        );
    }

    #[test]
    fn test_client_hint_ua_v103() {
        let result = generate_sec_ch_ua(Brand::Chrome, 103);
        assert_eq!(
            result,
            "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\""
        );
    }

    #[test]
    fn test_client_hint_ua_v104() {
        let result = generate_sec_ch_ua(Brand::Chrome, 104);
        assert_eq!(
            result,
            "\"Chromium\";v=\"104\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"104\""
        );
    }

    #[test]
    fn test_client_hint_ua_v105() {
        let result = generate_sec_ch_ua(Brand::Chrome, 105);
        assert_eq!(
            result,
            "\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\""
        );
    }

    #[test]
    fn test_client_hint_ua_v106() {
        let result = generate_sec_ch_ua(Brand::Chrome, 106);
        assert_eq!(
            result,
            "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\""
        );
    }

    #[test]
    fn test_client_hint_ua_v107() {
        let result = generate_sec_ch_ua(Brand::Chrome, 107);
        assert_eq!(
            result,
            "\"Google Chrome\";v=\"107\", \"Chromium\";v=\"107\", \"Not=A?Brand\";v=\"24\""
        );
    }

    #[test]
    fn test_client_hint_ua_v108() {
        let result = generate_sec_ch_ua(Brand::Chrome, 108);
        assert_eq!(
            result,
            "\"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"108\", \"Google Chrome\";v=\"108\""
        );
    }

    #[test]
    fn test_greased_brand_legacy() {
        assert_eq!(generate_greased_brand(80), " Not A;Brand");
        assert_eq!(generate_greased_brand(98), " Not A;Brand");
        assert_eq!(generate_greased_brand(99), " Not A;Brand");
        assert_eq!(generate_greased_brand(100), " Not A;Brand");
        assert_eq!(generate_greased_brand(101), " Not A;Brand");
        assert_eq!(generate_greased_brand(102), " Not A;Brand");
        assert_eq!(generate_greased_brand(103), ".Not/A)Brand");
        assert_eq!(generate_greased_brand(104), " Not A;Brand");
    }

    #[test]
    fn test_greased_brand_v105_plus() {
        assert_eq!(generate_greased_brand(105), "Not)A;Brand");
        assert_eq!(generate_greased_brand(106), "Not;A=Brand");
        assert_eq!(generate_greased_brand(107), "Not=A?Brand");
        assert_eq!(generate_greased_brand(108), "Not?A_Brand");
    }

    #[test]
    fn test_greased_version() {
        // Legacy versions always use "99"
        assert_eq!(get_greased_version(80), "99");
        assert_eq!(get_greased_version(100), "99");
        assert_eq!(get_greased_version(104), "99");

        // v105+ cycles through [8, 99, 24]
        assert_eq!(get_greased_version(105), "8");  // 105 % 3 = 0
        assert_eq!(get_greased_version(106), "99"); // 106 % 3 = 1
        assert_eq!(get_greased_version(107), "24"); // 107 % 3 = 2
        assert_eq!(get_greased_version(108), "8");  // 108 % 3 = 0
    }

    #[test]
    fn test_format_brand() {
        assert_eq!(
            format_brand("Google Chrome", "120"),
            "\"Google Chrome\";v=\"120\""
        );
        assert_eq!(format_brand("Chromium", "120"), "\"Chromium\";v=\"120\"");
    }

    #[test]
    fn test_sec_ch_ua_edge() {
        let result = generate_sec_ch_ua(Brand::Edge, 120);
        assert!(result.contains("Microsoft Edge"));
        assert!(result.contains("Chromium"));
    }

    #[test]
    fn test_sec_ch_ua_brave() {
        let result = generate_sec_ch_ua(Brand::Brave, 120);
        assert!(result.contains("Brave"));
        assert!(result.contains("Chromium"));
    }

    #[test]
    fn test_permutation_determinism() {
        // Same version should always produce same result
        let result1 = generate_sec_ch_ua(Brand::Chrome, 120);
        let result2 = generate_sec_ch_ua(Brand::Chrome, 120);
        assert_eq!(result1, result2);
    }

    #[test]
    fn test_full_version_list() {
        let result = generate_sec_ch_ua_full_version(Brand::Chrome, 120, "120.0.6099.129");
        assert!(result.contains("120.0.6099.129"));
    }

    #[test]
    fn test_brand_as_str() {
        assert_eq!(Brand::Chrome.as_str(), "Google Chrome");
        assert_eq!(Brand::Edge.as_str(), "Microsoft Edge");
        assert_eq!(Brand::Brave.as_str(), "Brave");
        assert_eq!(Brand::Opera.as_str(), "Opera");
        assert_eq!(Brand::Vivaldi.as_str(), "Vivaldi");
        assert_eq!(Brand::Samsung.as_str(), "Samsung Internet");
        assert_eq!(Brand::Yandex.as_str(), "Yandex Browser");
        assert_eq!(Brand::Chromium.as_str(), "Chromium");
    }

    #[test]
    fn test_brand_display() {
        assert_eq!(format!("{}", Brand::Chrome), "Google Chrome");
        assert_eq!(format!("{}", Brand::Edge), "Microsoft Edge");
    }
}
