// AST (Abstract Syntax Tree) for .gctf files
// Represents the parsed structure of a .gctf test file

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Complete .gctf document
/// Documents linked by `--- NEW ---` form a singly-linked list.
/// When `next_document` is `None` this is either a single-document file
/// or the last document in a chain.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GctfDocument {
    /// File path (absolute or relative)
    pub file_path: String,

    /// All sections in the document (preserving order)
    pub sections: Vec<Section>,

    /// Document metadata
    pub metadata: DocumentMetadata,

    /// Next document in chain (linked by `--- NEW ---`), `None` if single/last
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub next_document: Option<Box<GctfDocument>>,
}

/// Document metadata
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DocumentMetadata {
    /// Original file content (for error reporting)
    pub source: Option<String>,

    /// File modification time (for caching)
    pub mtime: Option<i64>,

    /// Parsed at timestamp
    pub parsed_at: i64,
}

/// File-level metadata (META section)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct FileMeta {
    /// Test name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// Test summary (one-liner)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,
    /// Test tags
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub tags: Vec<String>,
    /// Test owner (team/person)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub owner: Option<String>,
    /// Related links (docs, jira, etc)
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub links: Vec<String>,
}

impl FileMeta {
    /// Check if meta has any content
    pub fn is_empty(&self) -> bool {
        self.name.is_none()
            && self.summary.is_none()
            && self.tags.is_empty()
            && self.owner.is_none()
            && self.links.is_empty()
    }
}

/// A section in the .gctf file
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Section {
    /// Section type
    pub section_type: SectionType,

    /// Content of the section (raw text, typically JSON)
    pub content: SectionContent,

    /// Inline options (for sections that support them)
    pub inline_options: InlineOptions,

    /// Raw text content of the section (preserved for formatting)
    pub raw_content: String,

    /// Line number where section starts
    pub start_line: usize,

    /// Line number where section ends
    pub end_line: usize,
}

/// Section content
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SectionContent {
    /// Single value (ADDRESS, ENDPOINT, etc.)
    Single(String),

    /// JSON object (REQUEST, RESPONSE, ERROR)
    Json(serde_json::Value),

    /// Newline-delimited JSON values within a single section block
    JsonLines(Vec<serde_json::Value>),

    /// Key-value pairs (REQUEST_HEADERS, TLS, OPTIONS, PROTO)
    KeyValues(HashMap<String, String>),

    /// Extract variables from response (EXTRACT)
    Extract(HashMap<String, String>),

    /// Assertion expressions (ASSERTS)
    Assertions(Vec<String>),

    /// File-level metadata (META)
    Meta(FileMeta),

    /// Empty section
    Empty,
}

/// Section types in .gctf files
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SectionType {
    /// Server address
    Address,

    /// gRPC endpoint (service/method)
    Endpoint,

    /// Request payload (can have multiple)
    Request,

    /// Expected response (can have multiple)
    Response,

    /// Expected error
    Error,

    /// Request-specific headers
    RequestHeaders,

    /// Assertion expressions (can have multiple)
    Asserts,

    /// Protocol buffer configuration
    Proto,

    /// TLS/mTLS configuration
    Tls,

    /// Test execution options
    Options,

    /// Extract variables from response
    Extract,

    /// File-level metadata (suite, tags)
    Meta,
}

impl SectionType {
    /// Returns `true` if this section marks the end of a logical request-response cycle.
    pub fn is_terminal(&self) -> bool {
        matches!(
            self,
            SectionType::Response | SectionType::Error | SectionType::Asserts
        )
    }

    /// Get section name as string
    pub fn as_str(&self) -> &'static str {
        match self {
            SectionType::Address => "ADDRESS",
            SectionType::Endpoint => "ENDPOINT",
            SectionType::Request => "REQUEST",
            SectionType::Response => "RESPONSE",
            SectionType::Error => "ERROR",
            SectionType::RequestHeaders => "REQUEST_HEADERS",
            SectionType::Asserts => "ASSERTS",
            SectionType::Proto => "PROTO",
            SectionType::Tls => "TLS",
            SectionType::Options => "OPTIONS",
            SectionType::Extract => "EXTRACT",
            SectionType::Meta => "META",
        }
    }

    /// Parse section name string to SectionType
    pub fn from_keyword(s: &str) -> Option<SectionType> {
        match s.trim() {
            "ADDRESS" => Some(SectionType::Address),
            "ENDPOINT" => Some(SectionType::Endpoint),
            "REQUEST" => Some(SectionType::Request),
            "RESPONSE" => Some(SectionType::Response),
            "ERROR" => Some(SectionType::Error),
            "REQUEST_HEADERS" | "HEADERS" => Some(SectionType::RequestHeaders),
            "ASSERTS" => Some(SectionType::Asserts),
            "PROTO" => Some(SectionType::Proto),
            "TLS" => Some(SectionType::Tls),
            "OPTIONS" => Some(SectionType::Options),
            "EXTRACT" => Some(SectionType::Extract),
            "META" => Some(SectionType::Meta),
            _ => None,
        }
    }

    /// Check if section can appear multiple times
    pub fn is_multiple_allowed(&self) -> bool {
        matches!(
            self,
            SectionType::Request
                | SectionType::Response
                | SectionType::Asserts
                | SectionType::Extract
        )
    }

    /// Check if section is file-level (not inside documents)
    pub fn is_file_level(&self) -> bool {
        matches!(self, SectionType::Meta)
    }

    /// Check if section supports inline options
    pub fn supports_inline_options(&self) -> bool {
        matches!(self, SectionType::Response | SectionType::Error)
    }
}

/// Inline options for sections
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct InlineOptions {
    /// Run ASSERTS on same response (unary RPC)
    pub with_asserts: bool,

    /// Subset comparison (expected is subset of actual)
    pub partial: bool,

    /// Numeric tolerance for floating-point comparisons
    pub tolerance: Option<f64>,

    /// Remove sensitive fields before comparison
    pub redact: Vec<String>,

    /// Sort arrays for order-independent comparison
    pub unordered_arrays: bool,
}

impl InlineOptions {
    pub fn to_header_tokens(&self) -> Vec<String> {
        let mut parts = Vec::new();

        if self.partial {
            parts.push("partial".to_string());
        }

        if let Some(tolerance) = self.tolerance {
            parts.push(format!("tolerance={}", tolerance));
        }

        if !self.redact.is_empty() {
            let mut sorted_redact = self.redact.clone();
            sorted_redact.sort();
            let quoted = sorted_redact
                .iter()
                .map(|field| format!("\"{}\"", field))
                .collect::<Vec<_>>()
                .join(",");
            parts.push(format!("redact=[{}]", quoted));
        }

        if self.unordered_arrays {
            parts.push("unordered_arrays".to_string());
        }

        if self.with_asserts {
            parts.push("with_asserts".to_string());
        }

        parts
    }

    pub fn is_empty(&self) -> bool {
        !self.with_asserts
            && !self.partial
            && self.tolerance.is_none()
            && self.redact.is_empty()
            && !self.unordered_arrays
    }
}

impl Section {
    pub fn format_header(&self) -> String {
        let section = self.section_type.as_str();
        if self.section_type.supports_inline_options() {
            let parts = self.inline_options.to_header_tokens();
            if parts.is_empty() {
                format!("--- {} ---", section)
            } else {
                format!("--- {} {} ---", section, parts.join(" "))
            }
        } else {
            format!("--- {} ---", section)
        }
    }

    pub fn header_keyword_from_source<'a>(&self, source: &'a str) -> Option<&'a str> {
        let header_line = source.lines().nth(self.start_line)?.trim();
        let inner = header_line.strip_prefix("---")?.strip_suffix("---")?.trim();
        inner.split_whitespace().next()
    }
}

/// GCTF file header with inline options
/// Format: --- SECTION_NAME key=value ... ---
#[derive(Debug, Clone, PartialEq)]
pub struct SectionHeader {
    /// Section type
    pub section_type: SectionType,

    /// Inline options (key=value pairs)
    pub options: HashMap<String, String>,
}

/// Iterator over a linked document chain
pub struct DocumentChainIter<'a> {
    current: Option<&'a GctfDocument>,
}

impl<'a> Iterator for DocumentChainIter<'a> {
    type Item = &'a GctfDocument;

    fn next(&mut self) -> Option<Self::Item> {
        self.current.take().inspect(|doc| {
            self.current = doc.next_document.as_deref();
        })
    }
}

impl GctfDocument {
    /// Create a new empty document
    pub fn new(file_path: String) -> Self {
        Self {
            file_path,
            sections: Vec::new(),
            metadata: DocumentMetadata {
                source: None,
                mtime: None,
                parsed_at: crate::time::now_timestamp(),
            },
            next_document: None,
        }
    }

    /// Iterate over the document chain (single or `--- NEW ---` linked)
    pub fn iter_chain(&self) -> DocumentChainIter<'_> {
        DocumentChainIter {
            current: Some(self),
        }
    }

    /// Count documents in the chain
    pub fn document_count(&self) -> usize {
        self.iter_chain().count()
    }

    /// Check if this is a single-document file
    pub fn is_single_document(&self) -> bool {
        self.next_document.is_none()
    }

    /// Get document by index (0-based) from the chain
    pub fn get_document(&self, index: usize) -> Option<&GctfDocument> {
        self.iter_chain().nth(index)
    }

    /// Get all sections of a specific type
    pub fn sections_by_type(&self, section_type: SectionType) -> Vec<&Section> {
        self.sections
            .iter()
            .filter(|s| s.section_type == section_type)
            .collect()
    }

    /// Get first section of a specific type
    pub fn first_section(&self, section_type: SectionType) -> Option<&Section> {
        self.sections
            .iter()
            .find(|s| s.section_type == section_type)
    }

    /// Get address (from ADDRESS section or environment variable)
    pub fn get_address(&self, env_address: Option<&str>) -> Option<String> {
        if let Some(section) = self.first_section(SectionType::Address)
            && let SectionContent::Single(addr) = &section.content
        {
            return Some(addr.clone());
        }
        env_address.map(|s| s.to_string())
    }

    /// Get endpoint
    pub fn get_endpoint(&self) -> Option<String> {
        if let Some(section) = self.first_section(SectionType::Endpoint)
            && let SectionContent::Single(endpoint) = &section.content
        {
            return Some(endpoint.clone());
        }
        None
    }

    /// Parse endpoint into package, service, method
    pub fn parse_endpoint(&self) -> Option<(String, String, String)> {
        let endpoint = self.get_endpoint()?;
        let parts: Vec<&str> = endpoint.split('/').collect();
        if parts.len() == 2 {
            let full_service = parts[0];
            let service_parts: Vec<&str> = full_service.split('.').collect();
            if service_parts.len() >= 2 {
                let package = service_parts[..service_parts.len() - 1].join(".");
                let service = service_parts[service_parts.len() - 1].to_string();
                let method = parts[1].to_string();
                return Some((package, service, method));
            } else if service_parts.len() == 1 {
                let package = String::new();
                let service = service_parts[0].to_string();
                let method = parts[1].to_string();
                return Some((package, service, method));
            }
        }
        None
    }

    /// Get all request payloads
    pub fn get_requests(&self) -> Vec<serde_json::Value> {
        self.sections_by_type(SectionType::Request)
            .into_iter()
            .filter_map(|s| {
                if let SectionContent::Json(json) = &s.content {
                    Some(json.clone())
                } else {
                    None
                }
            })
            .collect()
    }

    /// Get all assertion sections
    pub fn get_assertions(&self) -> Vec<Vec<String>> {
        self.sections_by_type(SectionType::Asserts)
            .into_iter()
            .filter_map(|s| {
                if let SectionContent::Assertions(asserts) = &s.content {
                    Some(asserts.clone())
                } else {
                    None
                }
            })
            .collect()
    }

    /// Get request headers
    pub fn get_request_headers(&self) -> Option<HashMap<String, String>> {
        if let Some(section) = self.first_section(SectionType::RequestHeaders)
            && let SectionContent::KeyValues(headers) = &section.content
        {
            return Some(headers.clone());
        }
        None
    }

    /// Get TLS configuration
    pub fn get_tls_config(&self) -> Option<HashMap<String, String>> {
        if let Some(section) = self.first_section(SectionType::Tls)
            && let SectionContent::KeyValues(config) = &section.content
        {
            return Some(config.clone());
        }
        None
    }

    /// Get OPTIONS configuration
    pub fn get_options(&self) -> Option<HashMap<String, String>> {
        if let Some(section) = self.first_section(SectionType::Options)
            && let SectionContent::KeyValues(config) = &section.content
        {
            return Some(config.clone());
        }
        None
    }

    /// Get TLS configuration merged with defaults (section values override defaults)
    pub fn get_tls_config_with_defaults(
        &self,
        defaults: &HashMap<String, String>,
    ) -> Option<HashMap<String, String>> {
        let mut merged = defaults.clone();

        if let Some(section) = self.first_section(SectionType::Tls)
            && let SectionContent::KeyValues(config) = &section.content
        {
            for (key, value) in config {
                merged.insert(key.clone(), value.clone());
            }
        }

        if merged.is_empty() {
            None
        } else {
            Some(merged)
        }
    }

    /// Get PROTO configuration
    pub fn get_proto_config(&self) -> Option<HashMap<String, String>> {
        if let Some(section) = self.first_section(SectionType::Proto)
            && let SectionContent::KeyValues(config) = &section.content
        {
            return Some(config.clone());
        }
        None
    }

    /// Check for RESPONSE and ERROR conflict
    pub fn has_response_error_conflict(&self) -> bool {
        self.first_section(SectionType::Response).is_some()
            && self.first_section(SectionType::Error).is_some()
    }

    pub fn section_uses_deprecated_headers_alias(&self, section: &Section) -> bool {
        if section.section_type != SectionType::RequestHeaders {
            return false;
        }

        self.metadata
            .source
            .as_deref()
            .and_then(|source| section.header_keyword_from_source(source))
            .is_some_and(|keyword| keyword.eq_ignore_ascii_case("HEADERS"))
    }
}

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

    #[test]
    fn test_section_type_from_str() {
        assert_eq!(
            SectionType::from_keyword("ADDRESS"),
            Some(SectionType::Address)
        );
        assert_eq!(
            SectionType::from_keyword("ENDPOINT"),
            Some(SectionType::Endpoint)
        );
        assert_eq!(SectionType::from_keyword("INVALID"), None);
    }

    #[test]
    fn test_section_type_multiple_allowed() {
        assert!(SectionType::Request.is_multiple_allowed());
        assert!(SectionType::Response.is_multiple_allowed());
        assert!(SectionType::Asserts.is_multiple_allowed());
        assert!(!SectionType::Address.is_multiple_allowed());
        assert!(!SectionType::Endpoint.is_multiple_allowed());
    }

    #[test]
    fn test_section_type_supports_inline_options() {
        assert!(SectionType::Response.supports_inline_options());
        assert!(SectionType::Error.supports_inline_options());
        assert!(!SectionType::Request.supports_inline_options());
        assert!(!SectionType::Address.supports_inline_options());
    }

    #[test]
    fn test_section_type_as_str() {
        assert_eq!(SectionType::Address.as_str(), "ADDRESS");
        assert_eq!(SectionType::Endpoint.as_str(), "ENDPOINT");
        assert_eq!(SectionType::Request.as_str(), "REQUEST");
        assert_eq!(SectionType::Response.as_str(), "RESPONSE");
        assert_eq!(SectionType::Error.as_str(), "ERROR");
        assert_eq!(SectionType::RequestHeaders.as_str(), "REQUEST_HEADERS");
        assert_eq!(SectionType::Asserts.as_str(), "ASSERTS");
        assert_eq!(SectionType::Proto.as_str(), "PROTO");
        assert_eq!(SectionType::Tls.as_str(), "TLS");
        assert_eq!(SectionType::Options.as_str(), "OPTIONS");
        assert_eq!(SectionType::Extract.as_str(), "EXTRACT");
    }

    #[test]
    fn test_section_type_from_keyword_aliases() {
        assert_eq!(
            SectionType::from_keyword("HEADERS"),
            Some(SectionType::RequestHeaders)
        );
        assert_eq!(
            SectionType::from_keyword("REQUEST_HEADERS"),
            Some(SectionType::RequestHeaders)
        );
    }

    #[test]
    fn test_section_type_from_keyword_case_insensitive() {
        // Should be case sensitive based on implementation
        assert_eq!(SectionType::from_keyword("address"), None);
        assert_eq!(
            SectionType::from_keyword("  ADDRESS  "),
            Some(SectionType::Address)
        );
    }

    #[test]
    fn test_section_type_is_terminal() {
        assert!(SectionType::Response.is_terminal());
        assert!(SectionType::Error.is_terminal());
        assert!(SectionType::Asserts.is_terminal());
        assert!(!SectionType::Request.is_terminal());
        assert!(!SectionType::Endpoint.is_terminal());
        assert!(!SectionType::Extract.is_terminal());
        assert!(!SectionType::Address.is_terminal());
    }

    #[test]
    fn test_gctf_document_new() {
        let doc = GctfDocument::new("test.gctf".to_string());
        assert_eq!(doc.file_path, "test.gctf");
        assert!(doc.sections.is_empty());
        assert!(doc.metadata.source.is_none());
        assert!(doc.metadata.mtime.is_none());
    }

    #[test]
    fn test_gctf_document_sections_by_type() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Request,
            content: SectionContent::Json(json!({"key": "value1"})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });
        doc.sections.push(Section {
            section_type: SectionType::Request,
            content: SectionContent::Json(json!({"key": "value2"})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 3,
            end_line: 4,
        });
        doc.sections.push(Section {
            section_type: SectionType::Response,
            content: SectionContent::Json(json!({"result": "ok"})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 5,
            end_line: 6,
        });

        let requests = doc.sections_by_type(SectionType::Request);
        assert_eq!(requests.len(), 2);

        let responses = doc.sections_by_type(SectionType::Response);
        assert_eq!(responses.len(), 1);

        let errors = doc.sections_by_type(SectionType::Error);
        assert_eq!(errors.len(), 0);
    }

    #[test]
    fn test_gctf_document_first_section() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Request,
            content: SectionContent::Json(json!({"key": "value"})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });

        let first_request = doc.first_section(SectionType::Request);
        assert!(first_request.is_some());

        let first_error = doc.first_section(SectionType::Error);
        assert!(first_error.is_none());
    }

    #[test]
    fn test_gctf_document_get_address() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Address,
            content: SectionContent::Single("localhost:4770".to_string()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 1,
        });

        assert_eq!(doc.get_address(None), Some("localhost:4770".to_string()));
        assert_eq!(
            doc.get_address(Some("env:5000")),
            Some("localhost:4770".to_string())
        );

        let doc2 = GctfDocument::new("test.gctf".to_string());
        assert_eq!(
            doc2.get_address(Some("env:5000")),
            Some("env:5000".to_string())
        );
        assert_eq!(doc2.get_address(None), None);
    }

    #[test]
    fn test_gctf_document_get_endpoint() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Endpoint,
            content: SectionContent::Single("my.Service/Method".to_string()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 1,
        });

        assert_eq!(doc.get_endpoint(), Some("my.Service/Method".to_string()));

        let doc2 = GctfDocument::new("test.gctf".to_string());
        assert_eq!(doc2.get_endpoint(), None);
    }

    #[test]
    fn test_gctf_document_parse_endpoint() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Endpoint,
            content: SectionContent::Single("package.Service/Method".to_string()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 1,
        });

        let (package, service, method) = doc.parse_endpoint().unwrap();
        assert_eq!(package, "package");
        assert_eq!(service, "Service");
        assert_eq!(method, "Method");
    }

    #[test]
    fn test_gctf_document_parse_endpoint_no_package() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Endpoint,
            content: SectionContent::Single("Service/Method".to_string()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 1,
        });

        let (package, service, method) = doc.parse_endpoint().unwrap();
        assert_eq!(package, "");
        assert_eq!(service, "Service");
        assert_eq!(method, "Method");
    }

    #[test]
    fn test_gctf_document_parse_endpoint_invalid() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Endpoint,
            content: SectionContent::Single("invalid".to_string()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 1,
        });

        assert!(doc.parse_endpoint().is_none());
    }

    #[test]
    fn test_gctf_document_get_requests() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Request,
            content: SectionContent::Json(json!({"key": "value1"})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });
        doc.sections.push(Section {
            section_type: SectionType::Request,
            content: SectionContent::Json(json!({"key": "value2"})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 3,
            end_line: 4,
        });

        let requests = doc.get_requests();
        assert_eq!(requests.len(), 2);
        assert_eq!(requests[0], json!({"key": "value1"}));
        assert_eq!(requests[1], json!({"key": "value2"}));
    }

    #[test]
    fn test_gctf_document_get_assertions() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Asserts,
            content: SectionContent::Assertions(vec![".id == 1".to_string()]),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });
        doc.sections.push(Section {
            section_type: SectionType::Asserts,
            content: SectionContent::Assertions(vec![".name == \"test\"".to_string()]),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 3,
            end_line: 4,
        });

        let assertions = doc.get_assertions();
        assert_eq!(assertions.len(), 2);
        assert_eq!(assertions[0], vec![".id == 1"]);
        assert_eq!(assertions[1], vec![".name == \"test\""]);
    }

    #[test]
    fn test_gctf_document_get_request_headers() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        let mut headers = HashMap::new();
        headers.insert("Authorization".to_string(), "Bearer token".to_string());
        doc.sections.push(Section {
            section_type: SectionType::RequestHeaders,
            content: SectionContent::KeyValues(headers.clone()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });

        let result = doc.get_request_headers().unwrap();
        assert_eq!(
            result.get("Authorization"),
            Some(&"Bearer token".to_string())
        );
    }

    #[test]
    fn test_gctf_document_get_tls_config() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        let mut config = HashMap::new();
        config.insert("ca_cert".to_string(), "/path/to/ca.pem".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Tls,
            content: SectionContent::KeyValues(config.clone()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });

        let result = doc.get_tls_config().unwrap();
        assert_eq!(result.get("ca_cert"), Some(&"/path/to/ca.pem".to_string()));
    }

    #[test]
    fn test_gctf_document_get_options() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        let mut options = HashMap::new();
        options.insert("dry_run".to_string(), "true".to_string());
        options.insert("timeout".to_string(), "10".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Options,
            content: SectionContent::KeyValues(options.clone()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });

        let result = doc.get_options().unwrap();
        assert_eq!(result.get("dry_run"), Some(&"true".to_string()));
        assert_eq!(result.get("timeout"), Some(&"10".to_string()));
    }

    #[test]
    fn test_gctf_document_get_tls_config_with_defaults_env_only() {
        let doc = GctfDocument::new("test.gctf".to_string());
        let mut defaults = HashMap::new();
        defaults.insert("server_name".to_string(), "example.com".to_string());

        let result = doc.get_tls_config_with_defaults(&defaults).unwrap();
        assert_eq!(result.get("server_name"), Some(&"example.com".to_string()));
    }

    #[test]
    fn test_gctf_document_get_tls_config_with_defaults_section_overrides() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        let mut config = HashMap::new();
        config.insert("insecure".to_string(), "true".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Tls,
            content: SectionContent::KeyValues(config),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });

        let mut defaults = HashMap::new();
        defaults.insert("insecure".to_string(), "false".to_string());
        defaults.insert("server_name".to_string(), "example.com".to_string());

        let result = doc.get_tls_config_with_defaults(&defaults).unwrap();
        assert_eq!(result.get("insecure"), Some(&"true".to_string()));
        assert_eq!(result.get("server_name"), Some(&"example.com".to_string()));
    }

    #[test]
    fn test_gctf_document_get_proto_config() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        let mut config = HashMap::new();
        config.insert("files".to_string(), "service.proto".to_string());
        doc.sections.push(Section {
            section_type: SectionType::Proto,
            content: SectionContent::KeyValues(config.clone()),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });

        let result = doc.get_proto_config().unwrap();
        assert_eq!(result.get("files"), Some(&"service.proto".to_string()));
    }

    #[test]
    fn test_gctf_document_has_response_error_conflict() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        assert!(!doc.has_response_error_conflict());

        doc.sections.push(Section {
            section_type: SectionType::Response,
            content: SectionContent::Json(json!({"result": "ok"})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 1,
            end_line: 2,
        });
        assert!(!doc.has_response_error_conflict());

        doc.sections.push(Section {
            section_type: SectionType::Error,
            content: SectionContent::Json(json!({"code": 5})),
            inline_options: InlineOptions::default(),
            raw_content: "".to_string(),
            start_line: 3,
            end_line: 4,
        });
        assert!(doc.has_response_error_conflict());
    }

    #[test]
    fn test_inline_options_default() {
        let options = InlineOptions::default();
        assert!(!options.with_asserts);
        assert!(!options.partial);
        assert!(options.tolerance.is_none());
        assert!(options.redact.is_empty());
        assert!(!options.unordered_arrays);
    }

    #[test]
    fn test_section_format_header_with_inline_options() {
        let section = Section {
            section_type: SectionType::Response,
            content: SectionContent::Json(serde_json::json!({"ok": true})),
            inline_options: InlineOptions {
                with_asserts: true,
                partial: true,
                tolerance: Some(0.1),
                redact: vec!["token".to_string()],
                unordered_arrays: true,
            },
            raw_content: "".to_string(),
            start_line: 0,
            end_line: 0,
        };

        let header = section.format_header();
        assert_eq!(
            header,
            "--- RESPONSE partial tolerance=0.1 redact=[\"token\"] unordered_arrays with_asserts ---"
        );
    }

    #[test]
    fn test_section_content_debug() {
        let content = SectionContent::Single("test".to_string());
        let debug_str = format!("{:?}", content);
        assert!(debug_str.contains("Single"));
    }

    #[test]
    fn test_section_header_keyword_from_source() {
        let section = Section {
            section_type: SectionType::Response,
            content: SectionContent::Json(serde_json::json!({"ok": true})),
            inline_options: InlineOptions::default(),
            raw_content: "{\"ok\":true}".to_string(),
            start_line: 0,
            end_line: 2,
        };

        let source = "--- RESPONSE with_asserts=true ---\n{\"ok\":true}\n";
        assert_eq!(section.header_keyword_from_source(source), Some("RESPONSE"));
    }

    #[test]
    fn test_document_detects_deprecated_headers_alias() {
        let mut doc = GctfDocument::new("test.gctf".to_string());
        doc.metadata.source = Some("--- HEADERS ---\nAuthorization: Bearer t\n".to_string());
        doc.sections.push(Section {
            section_type: SectionType::RequestHeaders,
            content: SectionContent::KeyValues(HashMap::from([(
                "Authorization".to_string(),
                "Bearer t".to_string(),
            )])),
            inline_options: InlineOptions::default(),
            raw_content: "Authorization: Bearer t".to_string(),
            start_line: 0,
            end_line: 2,
        });

        assert!(doc.section_uses_deprecated_headers_alias(&doc.sections[0]));
    }

    #[test]
    fn test_gctf_document_debug() {
        let doc = GctfDocument::new("test.gctf".to_string());
        let debug_str = format!("{:?}", doc);
        assert!(debug_str.contains("test.gctf"));
    }

    // ─── Document chain (linked-list) tests ───

    #[test]
    fn test_document_chain_single() {
        let doc = GctfDocument::new("test.gctf".to_string());
        assert!(doc.is_single_document());
        assert_eq!(doc.document_count(), 1);
    }

    #[test]
    fn test_document_chain_two_docs() {
        let mut doc1 = GctfDocument::new("test.gctf".to_string());
        let doc2 = GctfDocument::new("test.gctf".to_string());
        doc1.next_document = Some(Box::new(doc2));

        assert!(!doc1.is_single_document());
        assert_eq!(doc1.document_count(), 2);
    }

    #[test]
    fn test_document_chain_three_docs() {
        let mut doc3 = GctfDocument::new("test.gctf".to_string());
        doc3.file_path = "doc3".to_string();

        let mut doc2 = GctfDocument::new("test.gctf".to_string());
        doc2.file_path = "doc2".to_string();
        doc2.next_document = Some(Box::new(doc3));

        let mut doc1 = GctfDocument::new("test.gctf".to_string());
        doc1.file_path = "doc1".to_string();
        doc1.next_document = Some(Box::new(doc2));

        assert_eq!(doc1.document_count(), 3);

        let docs: Vec<_> = doc1.iter_chain().collect();
        assert_eq!(docs.len(), 3);
        assert_eq!(docs[0].file_path, "doc1");
        assert_eq!(docs[1].file_path, "doc2");
        assert_eq!(docs[2].file_path, "doc3");
    }

    #[test]
    fn test_document_chain_get_document() {
        let mut doc2 = GctfDocument::new("test.gctf".to_string());
        doc2.file_path = "doc2".to_string();

        let mut doc1 = GctfDocument::new("test.gctf".to_string());
        doc1.file_path = "doc1".to_string();
        doc1.next_document = Some(Box::new(doc2));

        assert_eq!(doc1.get_document(0).unwrap().file_path, "doc1");
        assert_eq!(doc1.get_document(1).unwrap().file_path, "doc2");
        assert!(doc1.get_document(2).is_none());
    }

    #[test]
    fn test_document_chain_iter_on_last() {
        let doc = GctfDocument::new("test.gctf".to_string());
        let docs: Vec<_> = doc.iter_chain().collect();
        assert_eq!(docs.len(), 1);
        assert_eq!(docs[0].file_path, "test.gctf");
    }
}
