use crate::parsing::parsed_rspql_query::{Operator, ParsedQuery, WindowDefinition};
use regex::Regex;
use std::collections::HashMap;

pub struct RSPQLParser {
    pub rspql_query: String,
}

impl RSPQLParser {
    pub fn new(query: String) -> Self {
        Self { rspql_query: query }
    }

    pub fn parse(&self) -> ParsedQuery {
        let mut parsed = ParsedQuery::new("".to_string());
        let mut sparql_lines: Vec<String> = Vec::new();
        let mut prefix_mapper: HashMap<String, String> = HashMap::new();

        // Compile regex patterns once outside the loop
        let register_re = Regex::new(r"REGISTER +([^ ]+) +<([^>]+)> AS").unwrap();
        let window_re = Regex::new(
            r"FROM +NAMED +WINDOW +([^ ]+) +ON +STREAM +([^ ]+) +\[RANGE +([^ ]+) +STEP +([^ ]+)\]",
        )
        .unwrap();
        let prefix_re = Regex::new(r"PREFIX +([^:]*): +<([^>]+)>").unwrap();

        for line in self.rspql_query.lines() {
            let trimmed_line = line.trim();
            if trimmed_line.starts_with("REGISTER") {
                for captures in register_re.captures_iter(trimmed_line) {
                    let op_str = captures.get(1).unwrap().as_str();
                    let name = captures.get(2).unwrap().as_str();
                    if let Some(operator) = Self::parse_operator(op_str) {
                        parsed.set_r2s(operator, name.to_string());
                    }
                }
            } else if trimmed_line.starts_with("FROM NAMED WINDOW") {
                for captures in window_re.captures_iter(trimmed_line) {
                    let window_name =
                        Self::unwrap(captures.get(1).unwrap().as_str(), &prefix_mapper);
                    let stream_name =
                        Self::unwrap(captures.get(2).unwrap().as_str(), &prefix_mapper);
                    let width = captures
                        .get(3)
                        .unwrap()
                        .as_str()
                        .parse::<i64>()
                        .unwrap_or(0);
                    let slide = captures
                        .get(4)
                        .unwrap()
                        .as_str()
                        .parse::<i64>()
                        .unwrap_or(0);
                    let window_def = WindowDefinition {
                        window_name,
                        stream_name,
                        width,
                        slide,
                    };
                    parsed.add_s2r_window(window_def);
                }
            } else {
                let mut sparql_line = trimmed_line.to_string();
                if sparql_line.starts_with("WINDOW") {
                    sparql_line = sparql_line.replace("WINDOW", "GRAPH");
                }
                if sparql_line.starts_with("PREFIX") {
                    for captures in prefix_re.captures_iter(&sparql_line) {
                        let prefix = captures.get(1).unwrap().as_str().to_string();
                        let iri = captures.get(2).unwrap().as_str().to_string();
                        prefix_mapper.insert(prefix, iri);
                    }
                }
                sparql_lines.push(sparql_line);
            }
        }
        parsed.set_sparql_query(sparql_lines.join("\n"));
        parsed
    }

    fn parse_operator(op_str: &str) -> Option<Operator> {
        match op_str {
            "RStream" => Some(Operator::RStream),
            "IStream" => Some(Operator::IStream),
            "DStream" => Some(Operator::DStream),
            _ => None,
        }
    }

    fn unwrap(prefixed_iri: &str, mapper: &HashMap<String, String>) -> String {
        let trimmed = prefixed_iri.trim();
        if trimmed.starts_with('<') && trimmed.ends_with('>') {
            trimmed[1..trimmed.len() - 1].to_string()
        } else {
            let parts: Vec<&str> = trimmed.split(':').collect();
            if parts.len() == 2 {
                if let Some(iri) = mapper.get(parts[0]) {
                    format!("{}{}", iri, parts[1])
                } else {
                    "".to_string()
                }
            } else {
                "".to_string()
            }
        }
    }
}
