use std::{
    collections::HashMap,
    path::PathBuf,
};

use http::Method;

use crate::{
    parser::{
        context,
        protocol::{
            SessionRequestTarget, ensure_session_protocol_compatible,
            infer_request_protocol, infer_ws_send_kind,
            is_graphql_document_content_type, remember_session_target,
            resolve_request_target, validate_graphql_variables_json,
            validate_mcp_object_json,
        },
        spans::template_error_to_span,
        variables::assign_request_variable,
    },
    request::{
        FragmentInclude, GraphqlTemplateOperation,
        HttpTemplateOperation, McpInitializeTemplate, McpTemplateCall,
        McpTemplateOperation, McpToolCallTemplate, RequestProtocol,
        RequestTemplate, RequestTemplateOperation, SseTemplateAction,
        SseTemplateOperation, VariableStore, WsSendKindTemplate,
        WsTemplateAction, WsTemplateOperation,
    },
    schema::SchemaRegistry,
};

use super::{
    Rule,
    ScannedRequest,
    ValidatedMcpCallKind,
    ValidatedSseActionKind,
    ValidatedWsActionKind,
    parse_assertions,
    parse_response_captures,
    scan_request_block,
    validate_graphql_request,
    validate_mcp_call,
    validate_protocol_directives,
    validate_sse_action,
    validate_ws_action,
};

pub(in crate::parser) fn parse_request_template(
    pair: pest::iterators::Pair<Rule>,
    collection_store: &VariableStore,
    global_headers: &HashMap<String, String>,
    global_queries: &HashMap<String, String>,
    global_callbacks: &[String],
    schema_registry: &SchemaRegistry,
    working_dir: &PathBuf,
    session_targets: &mut HashMap<String, SessionRequestTarget>,
) -> Result<RequestTemplate, pest::error::Error<Rule>> {
    let scanned_request = scan_request_block(
        pair,
        collection_store,
        global_callbacks.to_vec(),
        |store, key, raw_value, span| {
            assign_request_variable(store, key, raw_value, working_dir)
                .map_err(|err| template_error_to_span(err, span))
        },
    )?;

    let has_graphql_directives = scanned_request.has_graphql_directives();
    let has_mcp_directives = scanned_request.has_mcp_directives();
    let has_ws_directives = scanned_request.has_ws_directives();
    let has_sse_directives = scanned_request.has_sse_directives();

    let ScannedRequest {
        request_span,
        store,
        description_raw,
        method,
        url_raw,
        header_raw,
        query_raw,
        form_text_raw,
        form_file_raw,
        protocol_raw,
        session_name_raw,
        operation_name_raw,
        graphql_variables_raw,
        mcp_call_raw,
        mcp_tool_raw,
        mcp_arguments_raw,
        ws_send_raw,
        sse_receive_seen,
        sse_within_raw,
        mcp_protocol_version_raw,
        mcp_client_name_raw,
        mcp_client_version_raw,
        mcp_capabilities_raw,
        body_raw,
        body_content_type_raw,
        callback_src_raw,
        dependencies,
        response_capture_raw,
        assertion_raw,
        fragment_includes_raw,
        fragment_guard_raw: _,
    } = scanned_request;

    let session_name_raw = session_name_raw.map(|(raw, _)| raw);
    let operation_name_raw = operation_name_raw.map(|(raw, _)| raw);
    let graphql_variables_raw = graphql_variables_raw.map(|(raw, _)| raw);
    let mcp_call_raw = mcp_call_raw.map(|(raw, _)| raw);
    let mcp_tool_raw = mcp_tool_raw.map(|(raw, _)| raw);
    let mcp_arguments_raw = mcp_arguments_raw.map(|(raw, _)| raw);
    let ws_send_raw = ws_send_raw.map(|(raw, _)| raw);
    let sse_within_raw = sse_within_raw.map(|(raw, _)| raw);
    let mcp_protocol_version_raw = mcp_protocol_version_raw.map(|(raw, _)| raw);
    let mcp_client_name_raw = mcp_client_name_raw.map(|(raw, _)| raw);
    let mcp_client_version_raw = mcp_client_version_raw.map(|(raw, _)| raw);
    let mcp_capabilities_raw = mcp_capabilities_raw.map(|(raw, _)| raw);
    let body_content_type_raw = body_content_type_raw.map(|(raw, _)| raw);

    let scalar_map = store.clone_scalars();

    let description_template = if description_raw.trim().is_empty() {
        "[No Description]".to_string()
    } else {
        description_raw.trim().to_string()
    };

    let group_key = if description_raw.trim().is_empty() {
        "[No Description]".to_string()
    } else {
        context::resolve_with_context(description_raw.trim(), &scalar_map)
    };

    let mut headers = HashMap::new();
    for (key, value) in global_headers {
        headers.insert(
            key.clone(),
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    for (key, value) in header_raw {
        headers.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let mut query_params = HashMap::new();
    for (key, value) in global_queries {
        query_params.insert(
            key.clone(),
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    for (key, value) in query_raw {
        query_params.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let mut form_text = HashMap::new();
    for (key, value) in form_text_raw {
        form_text.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let mut form_files = HashMap::new();
    for (key, value) in form_file_raw {
        form_files.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let body = body_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let body_content_type =
        body_content_type_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let operation_name =
        operation_name_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let graphql_variables =
        graphql_variables_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let session_name =
        session_name_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let mcp_call =
        mcp_call_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let mcp_tool =
        mcp_tool_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let mcp_arguments =
        mcp_arguments_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let mcp_protocol_version = mcp_protocol_version_raw
        .map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let mcp_client_name =
        mcp_client_name_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let mcp_client_version = mcp_client_version_raw
        .map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let mcp_capabilities =
        mcp_capabilities_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let ws_send =
        ws_send_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let sse_within =
        sse_within_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));

    let protocol = infer_request_protocol(
        protocol_raw
            .as_ref()
            .map(|(protocol, span)| (protocol.as_str(), span.clone())),
        session_name.as_deref(),
        has_mcp_directives,
        has_ws_directives,
        has_sse_directives,
        session_targets,
    )?;

    ensure_session_protocol_compatible(
        session_name.as_deref(),
        protocol,
        session_targets,
        request_span.clone(),
    )?;

    validate_protocol_directives(
        protocol,
        has_graphql_directives,
        has_mcp_directives,
        has_ws_directives,
        has_sse_directives,
        request_span.clone(),
    )?;

    let (method, url_raw) = resolve_request_target(
        method,
        url_raw,
        session_name.as_deref(),
        protocol,
        session_targets,
        request_span.clone(),
    )?;

    let callback_src = callback_src_raw
        .into_iter()
        .map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map))
        .collect();

    let response_captures = parse_response_captures(response_capture_raw, &scalar_map)?;
    let assertions = parse_assertions(assertion_raw, &scalar_map, schema_registry)?;

    let fragment_includes = fragment_includes_raw
        .into_iter()
        .map(|(path, guard, index)| FragmentInclude {
            path,
            guard,
            assertion_index: index,
        })
        .collect();

    let operation = match protocol {
        RequestProtocol::Http => {
            RequestTemplateOperation::Http(HttpTemplateOperation {
                method: method.clone(),
                url: url_raw.clone(),
                headers,
                query_params,
                form_text,
                form_files,
                body,
                body_content_type,
            })
        }
        RequestProtocol::Graphql => {
            validate_graphql_request(
                request_span.clone(),
                method.clone(),
                !form_text.is_empty() || !form_files.is_empty(),
                body.is_some(),
                body_content_type
                    .as_deref()
                    .map(is_graphql_document_content_type)
                    .unwrap_or(false),
                request_span.clone(),
                || {
                    if let Some(raw) = &graphql_variables {
                        validate_graphql_variables_json(raw.as_str(), request_span.clone())?;
                    }
                    Ok(())
                },
            )?;

            let document = body.expect("validated GraphQL document block");

            RequestTemplateOperation::Graphql(GraphqlTemplateOperation {
                http: HttpTemplateOperation {
                    method: method.clone(),
                    url: url_raw.clone(),
                    headers,
                    query_params,
                    form_text,
                    form_files,
                    body: None,
                    body_content_type: None,
                },
                operation_name,
                document,
                variables_json: graphql_variables,
            })
        }
        RequestProtocol::Mcp => {
            if method != Method::POST {
                return Err(pest::error::Error::<Rule>::new_from_span(
                    pest::error::ErrorVariant::CustomError {
                        message: "MCP-over-HTTP requests currently require POST".to_string(),
                    },
                    request_span.clone(),
                ));
            }

            if !form_text.is_empty()
                || !form_files.is_empty()
                || body.is_some()
                || body_content_type.is_some()
            {
                return Err(pest::error::Error::<Rule>::new_from_span(
                    pest::error::ErrorVariant::CustomError {
                        message: "MCP-over-HTTP requests do not support explicit body, content type, or form fields".to_string(),
                    },
                    request_span.clone(),
                ));
            }

            let call = match validate_mcp_call(
                request_span.clone(),
                mcp_call.as_deref(),
                mcp_tool.is_some(),
                mcp_arguments
                    .as_deref()
                    .map(|raw| (raw, request_span.clone())),
                mcp_protocol_version.is_some(),
                mcp_client_name.is_some(),
                mcp_client_version.is_some(),
                mcp_capabilities
                    .as_deref()
                    .map(|raw| (raw, request_span.clone())),
                request_span.clone(),
                |raw, field, span| validate_mcp_object_json(raw, field, span),
            )? {
                ValidatedMcpCallKind::Initialize => {
                    McpTemplateCall::Initialize(McpInitializeTemplate {
                        protocol_version: mcp_protocol_version,
                        client_name: mcp_client_name,
                        client_version: mcp_client_version,
                        capabilities_json: mcp_capabilities,
                    })
                }
                ValidatedMcpCallKind::ToolsList => McpTemplateCall::ToolsList,
                ValidatedMcpCallKind::ResourcesList => McpTemplateCall::ResourcesList,
                ValidatedMcpCallKind::ToolsCall => {
                    McpTemplateCall::ToolsCall(McpToolCallTemplate {
                        tool_name: mcp_tool.expect("validated MCP tool directive"),
                        arguments_json: mcp_arguments,
                    })
                }
            };

            RequestTemplateOperation::Mcp(McpTemplateOperation {
                http: HttpTemplateOperation {
                    method: method.clone(),
                    url: url_raw.clone(),
                    headers,
                    query_params,
                    form_text,
                    form_files,
                    body: None,
                    body_content_type: None,
                },
                call,
            })
        }
        RequestProtocol::Sse => {
            let action = match validate_sse_action(
                request_span.clone(),
                session_name.is_some(),
                method.clone(),
                (!form_text.is_empty()
                    || !form_files.is_empty()
                    || body.is_some()
                    || body_content_type.is_some())
                    .then_some((
                        "SSE requests do not support explicit body, content type, or form fields",
                        request_span.clone(),
                    )),
                sse_receive_seen,
                sse_within
                    .as_deref()
                    .map(|within| (within, request_span.clone())),
                request_span.clone(),
            )? {
                ValidatedSseActionKind::Open => SseTemplateAction::Open,
                ValidatedSseActionKind::Receive => SseTemplateAction::Receive {
                    within: sse_within.expect("validated SSE within directive"),
                },
            };

            RequestTemplateOperation::Sse(SseTemplateOperation {
                http: HttpTemplateOperation {
                    method: method.clone(),
                    url: url_raw.clone(),
                    headers,
                    query_params,
                    form_text,
                    form_files,
                    body: None,
                    body_content_type: None,
                },
                action,
            })
        }
        RequestProtocol::Ws => {
            let send_kind = infer_ws_send_kind(
                ws_send.as_deref(),
                body_content_type.as_deref(),
                body.is_some(),
            )
            .map_err(|message| {
                pest::error::Error::<Rule>::new_from_span(
                    pest::error::ErrorVariant::CustomError { message },
                    request_span.clone(),
                )
            })?;

            let action_kind = validate_ws_action(
                request_span.clone(),
                session_name.is_some(),
                method.clone(),
                (!form_text.is_empty() || !form_files.is_empty()).then_some((
                    "WebSocket requests do not support form fields",
                    request_span.clone(),
                )),
                sse_receive_seen,
                ws_send.is_some(),
                request_span.clone(),
                send_kind,
                body.is_some(),
                request_span.clone(),
                (body.is_some() || body_content_type.is_some()).then_some((
                    if sse_receive_seen {
                        "WebSocket receive steps do not support explicit body or content type blocks"
                    } else {
                        "WebSocket open steps do not support explicit body or content type blocks"
                    },
                    request_span.clone(),
                )),
                sse_within
                    .as_deref()
                    .map(|within| (within, request_span.clone())),
            )?;

            let action = match action_kind {
                ValidatedWsActionKind::Receive => WsTemplateAction::Receive {
                    within: sse_within.expect("validated WS receive within directive"),
                },
                ValidatedWsActionKind::Send | ValidatedWsActionKind::Exchange => {
                    let kind = send_kind.expect("validated WS send kind");
                    let payload = body.expect("validated WS send payload");

                    if matches!(kind, WsSendKindTemplate::Json) {
                        serde_json::from_str::<serde_json::Value>(payload.as_str()).map_err(|err| {
                            pest::error::Error::<Rule>::new_from_span(
                                pest::error::ErrorVariant::CustomError {
                                    message: format!("invalid WebSocket JSON payload: {}", err),
                                },
                                request_span.clone(),
                            )
                        })?;
                    }

                    if matches!(action_kind, ValidatedWsActionKind::Exchange) {
                        WsTemplateAction::Exchange {
                            kind,
                            payload,
                            within: sse_within.expect("validated WS exchange within directive"),
                        }
                    } else {
                        WsTemplateAction::Send { kind, payload }
                    }
                }
                ValidatedWsActionKind::Open => WsTemplateAction::Open,
            };

            RequestTemplateOperation::Ws(WsTemplateOperation {
                http: HttpTemplateOperation {
                    method: method.clone(),
                    url: url_raw.clone(),
                    headers,
                    query_params,
                    form_text,
                    form_files,
                    body: None,
                    body_content_type: None,
                },
                action,
            })
        }
    };

    remember_session_target(
        session_targets,
        session_name.as_deref(),
        protocol,
        &method,
        &url_raw,
    );

    Ok(RequestTemplate {
        group_key,
        description_template,
        operation,
        session_name,
        callback_src,
        response_captures,
        assertions,
        fragment_includes,
        declared_dependencies: dependencies,
        variable_store: store,
        working_dir: working_dir.clone(),
    })
}