use serde_json::Value;

use crate::MappingError;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JsonPathRoot {
    Mcp,
    RequestPath,
    RequestQuery,
    RequestHeaders,
    RequestBody,
    RequestVariables,
    RequestGrpc,
    ResponseBody,
    ResponseData,
    ResponseGrpc,
    Output,
}

impl JsonPathRoot {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Mcp => "mcp",
            Self::RequestPath => "request.path",
            Self::RequestQuery => "request.query",
            Self::RequestHeaders => "request.headers",
            Self::RequestBody => "request.body",
            Self::RequestVariables => "request.variables",
            Self::RequestGrpc => "request.grpc",
            Self::ResponseBody => "response.body",
            Self::ResponseData => "response.data",
            Self::ResponseGrpc => "response.grpc",
            Self::Output => "output",
        }
    }

    pub fn root_fields(&self) -> &'static [&'static str] {
        match self {
            Self::Mcp => &["mcp"],
            Self::RequestPath => &["request", "path"],
            Self::RequestQuery => &["request", "query"],
            Self::RequestHeaders => &["request", "headers"],
            Self::RequestBody => &["request", "body"],
            Self::RequestVariables => &["request", "variables"],
            Self::RequestGrpc => &["request", "grpc"],
            Self::ResponseBody => &["response", "body"],
            Self::ResponseData => &["response", "data"],
            Self::ResponseGrpc => &["response", "grpc"],
            Self::Output => &["output"],
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum JsonPathSegment {
    Field(String),
    Index(usize),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct JsonPath {
    pub root: JsonPathRoot,
    pub segments: Vec<JsonPathSegment>,
}

impl JsonPath {
    pub fn parse(path: &str) -> Result<Self, MappingError> {
        let body = path
            .strip_prefix("$.")
            .ok_or_else(|| MappingError::InvalidJsonPath {
                path: path.to_owned(),
            })?;

        let (root, rest) = match_root(body).ok_or_else(|| MappingError::InvalidJsonPath {
            path: path.to_owned(),
        })?;

        if !(rest.is_empty() || rest.starts_with('.') || rest.starts_with('[')) {
            return Err(MappingError::InvalidJsonPath {
                path: path.to_owned(),
            });
        }

        let segments = parse_segments(rest, path)?;

        Ok(Self { root, segments })
    }

    pub fn to_string_path(&self) -> String {
        let mut path = format!("$.{}", self.root.as_str());

        for segment in &self.segments {
            match segment {
                JsonPathSegment::Field(field) => {
                    path.push('.');
                    path.push_str(field);
                }
                JsonPathSegment::Index(index) => {
                    path.push('[');
                    path.push_str(&index.to_string());
                    path.push(']');
                }
            }
        }

        path
    }

    pub fn read<'a>(&self, value: &'a Value) -> Option<&'a Value> {
        let mut current = value;

        for field in self.root.root_fields() {
            current = current.get(*field)?;
        }

        for segment in &self.segments {
            current = match segment {
                JsonPathSegment::Field(field) => current.get(field)?,
                JsonPathSegment::Index(index) => current.get(*index)?,
            };
        }

        Some(current)
    }
}

fn match_root(input: &str) -> Option<(JsonPathRoot, &str)> {
    const ROOTS: [(&str, JsonPathRoot); 11] = [
        ("request.variables", JsonPathRoot::RequestVariables),
        ("request.headers", JsonPathRoot::RequestHeaders),
        ("response.body", JsonPathRoot::ResponseBody),
        ("response.data", JsonPathRoot::ResponseData),
        ("response.grpc", JsonPathRoot::ResponseGrpc),
        ("request.path", JsonPathRoot::RequestPath),
        ("request.query", JsonPathRoot::RequestQuery),
        ("request.body", JsonPathRoot::RequestBody),
        ("request.grpc", JsonPathRoot::RequestGrpc),
        ("output", JsonPathRoot::Output),
        ("mcp", JsonPathRoot::Mcp),
    ];

    for (prefix, root) in ROOTS {
        if let Some(rest) = input.strip_prefix(prefix) {
            return Some((root, rest));
        }
    }

    None
}

fn parse_segments(mut input: &str, original: &str) -> Result<Vec<JsonPathSegment>, MappingError> {
    let mut segments = Vec::new();

    while !input.is_empty() {
        if let Some(rest) = input.strip_prefix('.') {
            let end = rest.find(['.', '[']).unwrap_or(rest.len());
            let field = &rest[..end];

            if field.is_empty() || !is_valid_field(field) {
                return Err(MappingError::InvalidJsonPath {
                    path: original.to_owned(),
                });
            }

            segments.push(JsonPathSegment::Field(field.to_owned()));
            input = &rest[end..];
            continue;
        }

        if let Some(rest) = input.strip_prefix('[') {
            let Some(end) = rest.find(']') else {
                return Err(MappingError::InvalidJsonPath {
                    path: original.to_owned(),
                });
            };

            let index =
                rest[..end]
                    .parse::<usize>()
                    .map_err(|_| MappingError::InvalidJsonPath {
                        path: original.to_owned(),
                    })?;

            segments.push(JsonPathSegment::Index(index));
            input = &rest[end + 1..];
            continue;
        }

        return Err(MappingError::InvalidJsonPath {
            path: original.to_owned(),
        });
    }

    Ok(segments)
}

fn is_valid_field(value: &str) -> bool {
    value
        .chars()
        .all(|char| char.is_ascii_alphanumeric() || char == '_' || char == '-')
}

#[cfg(test)]
mod tests {
    use super::{JsonPath, JsonPathRoot, JsonPathSegment};

    #[test]
    fn parses_nested_jsonpath_with_array_index() {
        let path = JsonPath::parse("$.request.body.contacts[0].email").unwrap();

        assert_eq!(path.root, JsonPathRoot::RequestBody);
        assert_eq!(
            path.segments,
            vec![
                JsonPathSegment::Field("contacts".to_owned()),
                JsonPathSegment::Index(0),
                JsonPathSegment::Field("email".to_owned())
            ]
        );
    }

    #[test]
    fn rejects_unknown_root() {
        let error = JsonPath::parse("$.unknown.field").unwrap_err();

        assert_eq!(
            error,
            crate::MappingError::InvalidJsonPath {
                path: "$.unknown.field".to_owned(),
            }
        );
    }
}
