use std::collections::HashMap;

use http::Method;

use crate::request::VariableStore;

use crate::parser::declarations::reject_misplaced_declaration;

use super::Rule;

pub(in crate::parser) struct ScannedRequest<'a> {
    pub(in crate::parser) request_span: pest::Span<'a>,
    pub(in crate::parser) store: VariableStore,
    pub(in crate::parser) description_raw: String,
    pub(in crate::parser) method: Option<Method>,
    pub(in crate::parser) url_raw: Option<String>,
    pub(in crate::parser) header_raw: HashMap<String, String>,
    pub(in crate::parser) query_raw: HashMap<String, String>,
    pub(in crate::parser) form_text_raw: HashMap<String, String>,
    pub(in crate::parser) form_file_raw: HashMap<String, String>,
    pub(in crate::parser) protocol_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) session_name_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) operation_name_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) graphql_variables_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) mcp_call_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) mcp_tool_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) mcp_arguments_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) ws_send_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) sse_receive_seen: bool,
    pub(in crate::parser) sse_within_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) mcp_protocol_version_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) mcp_client_name_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) mcp_client_version_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) mcp_capabilities_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) body_raw: Option<String>,
    pub(in crate::parser) body_content_type_raw: Option<(String, pest::Span<'a>)>,
    pub(in crate::parser) callback_src_raw: Vec<String>,
    pub(in crate::parser) dependencies: Vec<String>,
    pub(in crate::parser) response_capture_raw: Vec<(String, pest::Span<'a>)>,
    pub(in crate::parser) assertion_raw: Vec<(String, pest::Span<'a>)>,
    pub(in crate::parser) fragment_includes_raw: Vec<(String, Option<String>, usize)>,
    pub(in crate::parser) fragment_guard_raw: Vec<(String, pest::Span<'a>)>,
}

impl<'a> ScannedRequest<'a> {
    pub(in crate::parser) fn has_graphql_directives(&self) -> bool {
        self.operation_name_raw.is_some() || self.graphql_variables_raw.is_some()
    }

    pub(in crate::parser) fn has_mcp_directives(&self) -> bool {
        self.mcp_call_raw.is_some()
            || self.mcp_tool_raw.is_some()
            || self.mcp_arguments_raw.is_some()
            || self.mcp_protocol_version_raw.is_some()
            || self.mcp_client_name_raw.is_some()
            || self.mcp_client_version_raw.is_some()
            || self.mcp_capabilities_raw.is_some()
    }

    pub(in crate::parser) fn has_ws_directives(&self) -> bool {
        self.ws_send_raw.is_some()
    }

    pub(in crate::parser) fn has_sse_directives(&self) -> bool {
        self.sse_receive_seen || self.sse_within_raw.is_some()
    }

    pub(in crate::parser) fn body_seen(&self) -> bool {
        self.body_raw.is_some()
    }
}

pub(in crate::parser) fn scan_request_block<'a, F>(
    pair: pest::iterators::Pair<'a, Rule>,
    collection_store: &VariableStore,
    initial_callbacks: Vec<String>,
    mut assign_request_variable: F,
) -> Result<ScannedRequest<'a>, pest::error::Error<Rule>>
where
    F: FnMut(
        &mut VariableStore,
        String,
        &str,
        pest::Span<'a>,
    ) -> Result<(), pest::error::Error<Rule>>,
{
    let request_span = pair.as_span();
    let mut scanned = ScannedRequest {
        request_span: request_span.clone(),
        store: collection_store.clone(),
        description_raw: String::new(),
        method: None,
        url_raw: None,
        header_raw: HashMap::new(),
        query_raw: HashMap::new(),
        form_text_raw: HashMap::new(),
        form_file_raw: HashMap::new(),
        protocol_raw: None,
        session_name_raw: None,
        operation_name_raw: None,
        graphql_variables_raw: None,
        mcp_call_raw: None,
        mcp_tool_raw: None,
        mcp_arguments_raw: None,
        ws_send_raw: None,
        sse_receive_seen: false,
        sse_within_raw: None,
        mcp_protocol_version_raw: None,
        mcp_client_name_raw: None,
        mcp_client_version_raw: None,
        mcp_capabilities_raw: None,
        body_raw: None,
        body_content_type_raw: None,
        callback_src_raw: initial_callbacks,
        dependencies: Vec::new(),
        response_capture_raw: Vec::new(),
        assertion_raw: Vec::new(),
        fragment_includes_raw: Vec::new(),
        fragment_guard_raw: Vec::new(),
    };

    for inner in pair.into_inner() {
        match inner.as_rule() {
            Rule::description => {
                reject_misplaced_declaration(inner.as_str(), inner.as_span())?;
                scanned.description_raw.push_str(inner.as_str().trim());
            }
            Rule::variable => {
                let span = inner.as_span();
                let mut inner_pairs = inner.into_inner();
                let key = inner_pairs.next().unwrap().as_str().trim().to_string();
                let raw_value = inner_pairs.next().unwrap().as_str().to_string();

                assign_request_variable(&mut scanned.store, key, raw_value.as_str(), span)?;
            }
            Rule::protocol_directive => {
                let span = inner.as_span();
                let mut protocol_pairs = inner.into_inner();
                scanned.protocol_raw = Some((
                    protocol_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::session_directive => {
                let span = inner.as_span();
                let mut session_pairs = inner.into_inner();
                scanned.session_name_raw = Some((
                    session_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::http_method => {
                scanned.method = Some(inner.as_str().parse().unwrap());
            }
            Rule::url => {
                scanned.url_raw = Some(inner.as_str().trim().to_string());
            }
            Rule::header => {
                let mut header_pairs = inner.into_inner();
                let key = header_pairs.next().unwrap().as_str().trim().to_string();
                let value = header_pairs.next().unwrap().as_str().trim().to_string();
                scanned.header_raw.insert(key, value);
            }
            Rule::query => {
                let mut query_pairs = inner.into_inner();
                let key = query_pairs.next().unwrap().as_str().trim().to_string();
                let value = query_pairs.next().unwrap().as_str().trim().to_string();
                scanned.query_raw.insert(key, value);
            }
            Rule::operation_directive => {
                let span = inner.as_span();
                let mut operation_pairs = inner.into_inner();
                scanned.operation_name_raw = Some((
                    operation_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::variables_directive => {
                let span = inner.as_span();
                let mut variables_pairs = inner.into_inner();
                scanned.graphql_variables_raw = Some((
                    variables_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::call_directive => {
                let span = inner.as_span();
                let mut call_pairs = inner.into_inner();
                scanned.mcp_call_raw = Some((
                    call_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::tool_directive => {
                let span = inner.as_span();
                let mut tool_pairs = inner.into_inner();
                scanned.mcp_tool_raw = Some((
                    tool_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::arguments_directive => {
                let span = inner.as_span();
                let mut arguments_pairs = inner.into_inner();
                scanned.mcp_arguments_raw = Some((
                    arguments_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::send_directive => {
                let span = inner.as_span();
                let mut send_pairs = inner.into_inner();
                scanned.ws_send_raw = Some((
                    send_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::receive_directive => {
                scanned.sse_receive_seen = true;
            }
            Rule::within_directive => {
                let span = inner.as_span();
                let mut within_pairs = inner.into_inner();
                scanned.sse_within_raw = Some((
                    within_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::protocol_version_directive => {
                let span = inner.as_span();
                let mut protocol_version_pairs = inner.into_inner();
                scanned.mcp_protocol_version_raw = Some((
                    protocol_version_pairs
                        .next()
                        .unwrap()
                        .as_str()
                        .trim()
                        .to_string(),
                    span,
                ));
            }
            Rule::client_name_directive => {
                let span = inner.as_span();
                let mut client_name_pairs = inner.into_inner();
                scanned.mcp_client_name_raw = Some((
                    client_name_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::client_version_directive => {
                let span = inner.as_span();
                let mut client_version_pairs = inner.into_inner();
                scanned.mcp_client_version_raw = Some((
                    client_version_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::capabilities_directive => {
                let span = inner.as_span();
                let mut capabilities_pairs = inner.into_inner();
                scanned.mcp_capabilities_raw = Some((
                    capabilities_pairs.next().unwrap().as_str().trim().to_string(),
                    span,
                ));
            }
            Rule::form => {
                let mut form_pairs = inner.into_inner();
                let key = form_pairs.next().unwrap().as_str().trim().to_string();
                let value = form_pairs.next().unwrap();
                match value.as_rule() {
                    Rule::file => {
                        let trimmed = value.as_str().trim_start_matches('@').trim().to_string();
                        scanned.form_file_raw.insert(key, trimmed);
                    }
                    Rule::text => {
                        scanned.form_text_raw.insert(key, value.as_str().to_string());
                    }
                    _ => unreachable!("unexpected rule: {:?}", value.as_rule()),
                }
            }
            Rule::body => {
                scanned.body_raw = Some(inner.as_str().to_string());
            }
            Rule::body_content_type => {
                scanned.body_content_type_raw =
                    Some((inner.as_str().trim().to_string(), inner.as_span()));
            }
            Rule::dependency => {
                scanned.dependencies.push(parse_dependency(inner.as_str()));
            }
            Rule::callback => {
                scanned
                    .callback_src_raw
                    .push(inner.as_str().strip_prefix('!').unwrap().to_string());
            }
            Rule::response_capture => {
                scanned
                    .response_capture_raw
                    .push((inner.as_str().to_string(), inner.as_span()));
            }
            Rule::assertion => {
                scanned
                    .assertion_raw
                    .push((inner.as_str().to_string(), inner.as_span()));
            }
            Rule::fragment_include => {
                let raw = inner.as_str().to_string();
                let (guard, path) = parse_fragment_include(raw.as_str());
                if path.is_empty() {
                    return Err(pest::error::Error::<Rule>::new_from_span(
                        pest::error::ErrorVariant::CustomError {
                            message: "fragment include is missing a target".to_string(),
                        },
                        inner.as_span(),
                    ));
                }
                if let Some(guard) = &guard {
                    scanned
                        .fragment_guard_raw
                        .push((guard.clone(), inner.as_span()));
                }
                scanned
                    .fragment_includes_raw
                    .push((path, guard, scanned.assertion_raw.len()));
            }
            _ => unreachable!("unexpected rule: {:?}", inner.as_rule()),
        }
    }

    Ok(scanned)
}

fn parse_fragment_include(raw: &str) -> (Option<String>, String) {
    let trimmed = raw.trim();
    if !trimmed.starts_with("<<") && !trimmed.starts_with('[') {
        let path = trimmed.trim_start_matches("<<").trim().to_string();
        return (None, path);
    }

    if let Some(remainder) = trimmed.strip_prefix("<<") {
        return (None, remainder.trim().to_string());
    }

    let mut chars = trimmed.chars().peekable();
    if chars.next() != Some('[') {
        return (None, trimmed.trim().to_string());
    }

    let mut guard = String::new();
    let mut in_single = false;
    let mut in_double = false;

    while let Some(ch) = chars.next() {
        match ch {
            '\\' => {
                if let Some(next) = chars.next() {
                    guard.push(next);
                }
            }
            '\'' if !in_double => {
                in_single = !in_single;
                guard.push(ch);
            }
            '"' if !in_single => {
                in_double = !in_double;
                guard.push(ch);
            }
            ']' if !in_single && !in_double => {
                let remainder: String = chars.collect();
                let remainder = remainder.trim();
                let path = remainder.trim_start_matches("<<").trim().to_string();
                return (Some(guard.trim().to_string()), path);
            }
            _ => guard.push(ch),
        }
    }

    (None, trimmed.trim().to_string())
}

fn parse_dependency(raw: &str) -> String {
    let remainder = raw
        .strip_prefix('>')
        .map(|s| s.trim())
        .unwrap_or(raw.trim());

    let remainder = remainder
        .strip_prefix("requires")
        .map(|s| s.trim_start())
        .unwrap_or(remainder);

    let remainder = remainder
        .strip_prefix(':')
        .map(|s| s.trim_start())
        .unwrap_or(remainder);

    let name = remainder.trim();

    if name.starts_with('"') && name.ends_with('"') && name.len() >= 2 {
        name[1..name.len() - 1].trim().to_string()
    } else {
        name.to_string()
    }
}