//! Read-only subquery executor for EXISTS and other non-mutating subqueries.

use std::collections::HashMap;

use crate::ast::*;
use crate::error::ExecutionResult;
use crate::executor::{ExecutionContext, ResultSet, Evaluator};
use crate::executor::pattern_matcher::PatternMatcher;
use crate::executor::functions::{FunctionRegistry, aggregate::create_accumulator};
use crate::graph::GraphBackend;
use crate::result::{QueryResult, QueryStats, Record, CypherValue};

/// Executor for read-only subqueries (EXISTS, pattern comprehensions, etc.)
/// This executor works with an immutable graph reference since it doesn't modify data.
pub struct SubqueryExecutor<'a, G: GraphBackend> {
    graph: &'a G,
    functions: &'a FunctionRegistry,
}

impl<'a, G: GraphBackend> SubqueryExecutor<'a, G> {
    /// Create a new read-only subquery executor
    pub fn new(graph: &'a G, functions: &'a FunctionRegistry) -> Self {
        Self { graph, functions }
    }

    /// Execute a query in read-only mode for EXISTS evaluation
    /// Returns true if the query produces any results, false otherwise
    pub fn execute_exists_query(
        &self,
        query: &Query,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<bool> {
        // Execute the query and check if it returns any rows
        let result = self.execute_query_readonly(query, ctx)?;
        Ok(!result.rows.is_empty())
    }

    /// Execute a full query in read-only mode
    /// This handles all read-only clauses: MATCH, WHERE, WITH, RETURN, etc.
    fn execute_query_readonly(
        &self,
        query: &Query,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<QueryResult> {
        if query.parts.len() == 1 {
            return self.execute_single_query_readonly(&query.parts[0], ctx);
        }

        // Handle UNION (read-only)
        let mut combined = QueryResult::new();
        for (i, part) in query.parts.iter().enumerate() {
            let result = self.execute_single_query_readonly(part, ctx)?;

            if i == 0 {
                combined.columns = result.columns.clone();
            }

            for row in result.rows {
                combined.rows.push(row);
            }

            combined.stats.merge(&result.stats);
        }

        if !query.union_all {
            // Remove duplicates for UNION (not UNION ALL)
            let mut seen = std::collections::HashSet::new();
            combined.rows.retain(|row| {
                let key = format!("{:?}", row);
                seen.insert(key)
            });
        }

        Ok(combined)
    }

    /// Execute a single query (sequence of clauses) in read-only mode
    fn execute_single_query_readonly(
        &self,
        query: &SingleQuery,
        parent_ctx: &ExecutionContext,
    ) -> ExecutionResult<QueryResult> {
        // Clone parent context to inherit outer variables
        let mut ctx = parent_ctx.clone();
        let mut result_set = ResultSet::single_empty();
        let stats = QueryStats::default();
        let mut return_clause = None;

        for clause in &query.clauses {
            match clause {
                Clause::Match(m) => {
                    result_set = self.execute_match_readonly(m, &ctx, false, result_set)?;
                }
                Clause::OptionalMatch(m) => {
                    result_set = self.execute_match_readonly(m, &ctx, true, result_set)?;
                }
                Clause::With(w) => {
                    result_set = self.execute_with_readonly(&w.projection, w.where_clause.as_ref(), &ctx, result_set)?;
                    // Update context for next clause
                    if let Some(record) = result_set.records.first() {
                        ctx.replace_bindings_from_record(record);
                    }
                }
                Clause::Return(r) => {
                    return_clause = Some(r.clone());
                    break;
                }
                // WHERE clauses are handled within MATCH, not as standalone clauses
                // Read-only subqueries should not encounter mutating clauses
                Clause::Create(_) | Clause::Delete(_) | Clause::Set(_) |
                Clause::Remove(_) | Clause::Merge(_) => {
                    return Err(crate::executor::ExecutionError::Internal(
                        "Mutating clause in read-only EXISTS subquery".to_string()
                    ));
                }
                // Skip other clauses for now
                _ => {}
            }
        }

        // Execute RETURN if present
        if let Some(ret) = return_clause {
            let result = self.execute_return_readonly(&ret.projection, &ctx, result_set)?;
            Ok(QueryResult {
                columns: result.0,
                rows: result.1,
                stats,
            })
        } else {
            // No RETURN clause - return empty result
            Ok(QueryResult {
                columns: vec![],
                rows: vec![],
                stats,
            })
        }
    }

    /// Execute a MATCH clause in read-only mode
    fn execute_match_readonly(
        &self,
        clause: &MatchClause,
        ctx: &ExecutionContext,
        optional: bool,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let matcher = PatternMatcher::new(self.graph, self.functions);
        let evaluator = Evaluator::new(self.graph, self.functions);

        let mut results = ResultSet::new();

        if current.is_empty() {
            // No input - match pattern from scratch
            let matches = matcher.match_pattern(&clause.pattern, ctx, optional)?;

            for record in matches.iter() {
                // Apply WHERE filter if present
                if let Some(where_expr) = &clause.where_clause {
                    let mut local_ctx = ctx.clone();
                    local_ctx.load_record(record);

                    match evaluator.evaluate(where_expr, &local_ctx)? {
                        CypherValue::Boolean(true) => results.add(record.clone()),
                        CypherValue::Boolean(false) => {}
                        _ => {}
                    }
                } else {
                    results.add(record.clone());
                }
            }
        } else {
            // Match pattern for each input record
            for input_record in current.iter() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(input_record);

                let matches = matcher.match_pattern(&clause.pattern, &local_ctx, optional)?;

                if matches.is_empty() && optional {
                    // OPTIONAL MATCH with no results - keep input with NULL for unmatched variables
                    results.add(input_record.clone());
                } else {
                    for match_record in matches.iter() {
                        let mut combined = input_record.clone();
                        // Merge match_record into combined
                        for col in match_record.columns() {
                            if let Some(val) = match_record.get(col) {
                                combined.add(col.clone(), val.clone());
                            }
                        }

                        // Apply WHERE filter
                        if let Some(where_expr) = &clause.where_clause {
                            let mut where_ctx = ctx.clone();
                            where_ctx.load_record(&combined);

                            match evaluator.evaluate(where_expr, &where_ctx)? {
                                CypherValue::Boolean(true) => results.add(combined),
                                CypherValue::Boolean(false) => {}
                                _ => {}
                            }
                        } else {
                            results.add(combined);
                        }
                    }
                }
            }
        }

        Ok(results)
    }

    /// Execute a WITH projection in read-only mode
    fn execute_with_readonly(
        &self,
        projection: &Projection,
        where_clause: Option<&Expression>,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let evaluator = Evaluator::new(self.graph, self.functions);

        // Check if this projection has aggregation
        let has_aggregation = self.projection_has_aggregation(projection);

        if has_aggregation {
            // Handle aggregation case
            self.execute_aggregation_readonly(projection, where_clause, &evaluator, ctx, current)
        } else {
            // Non-aggregation case
            let mut results = ResultSet::new();

            for record in current.iter() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(record);

                let mut new_record = Record::new();

                // Handle projection items
                match &projection.items {
                    ProjectionItems::All => {
                        new_record = record.clone();
                    }
                    ProjectionItems::Explicit(items) => {
                        for item in items {
                            let value = evaluator.evaluate(&item.expression, &local_ctx)?;
                            let alias = item.alias.clone().unwrap_or_else(|| format!("{:?}", item.expression));
                            new_record.add(alias, value);
                        }
                    }
                }

                // Apply WHERE filter if present
                if let Some(where_expr) = where_clause {
                    let mut filter_ctx = ctx.clone();
                    filter_ctx.load_record(&new_record);
                    let cond = evaluator.evaluate(where_expr, &filter_ctx)?;
                    if !cond.is_truthy() {
                        continue;
                    }
                }

                results.add(new_record);
            }

            Ok(results)
        }
    }

    /// Check if a projection has aggregation functions
    fn projection_has_aggregation(&self, projection: &Projection) -> bool {
        match &projection.items {
            ProjectionItems::All => false,
            ProjectionItems::Explicit(items) => {
                items.iter().any(|item| self.expr_has_aggregation(&item.expression))
            }
        }
    }

    /// Check if an expression contains aggregation
    fn expr_has_aggregation(&self, expr: &Expression) -> bool {
        match expr {
            Expression::FunctionCall(call) => {
                self.functions.is_aggregate(&call.full_name()) ||
                call.arguments.iter().any(|arg| self.expr_has_aggregation(arg))
            }
            Expression::CountAll => true,
            Expression::BinaryOp(left, _, right) => {
                self.expr_has_aggregation(left) || self.expr_has_aggregation(right)
            }
            Expression::UnaryOp(_, inner) => self.expr_has_aggregation(inner),
            Expression::List(items) => items.iter().any(|item| self.expr_has_aggregation(item)),
            _ => false,
        }
    }

    /// Execute aggregation in read-only mode
    fn execute_aggregation_readonly(
        &self,
        projection: &Projection,
        where_clause: Option<&Expression>,
        evaluator: &Evaluator<G>,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let items = match &projection.items {
            ProjectionItems::Explicit(items) => items,
            ProjectionItems::All => return Ok(current),
        };

        // Separate grouping and aggregate expressions
        let mut grouping_exprs: Vec<(&Expression, String)> = Vec::new();
        let mut aggregate_exprs: Vec<(&Expression, String)> = Vec::new();

        for item in items {
            let name = item.alias.clone().unwrap_or_else(|| format!("{:?}", item.expression));
            if self.expr_has_aggregation(&item.expression) {
                aggregate_exprs.push((&item.expression, name));
            } else {
                grouping_exprs.push((&item.expression, name));
            }
        }

        // Group records by grouping keys
        let mut groups: HashMap<String, Vec<Record>> = HashMap::new();

        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);

            // Build group key
            let mut key_parts = Vec::new();
            for (expr, _) in &grouping_exprs {
                let value = evaluator.evaluate(expr, &local_ctx)?;
                key_parts.push(format!("{:?}", value));
            }
            let key = key_parts.join("|");

            groups.entry(key).or_default().push(record.clone());
        }

        // If no grouping and no records, create one group for aggregates like count(*)
        if grouping_exprs.is_empty() && groups.is_empty() {
            groups.insert(String::new(), current.records);
        }

        // Compute results for each group
        let mut results = ResultSet::new();

        for (_key, group_records) in groups {
            let mut result_record = Record::new();

            // Add grouping values
            if let Some(first) = group_records.first() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(first);

                for (expr, name) in &grouping_exprs {
                    let value = evaluator.evaluate(expr, &local_ctx)?;
                    result_record.add(name.clone(), value);
                }
            }

            // Compute aggregates
            for (expr, name) in &aggregate_exprs {
                let value = self.compute_aggregate_readonly(expr, evaluator, ctx, &group_records)?;
                result_record.add(name.clone(), value);
            }

            // Apply WHERE filter if present
            if let Some(where_expr) = where_clause {
                let mut filter_ctx = ctx.clone();
                filter_ctx.load_record(&result_record);
                let cond = evaluator.evaluate(where_expr, &filter_ctx)?;
                if !cond.is_truthy() {
                    continue;
                }
            }

            results.add(result_record);
        }

        Ok(results)
    }

    /// Compute an aggregate expression over a set of records (read-only)
    fn compute_aggregate_readonly(
        &self,
        expr: &Expression,
        evaluator: &Evaluator<G>,
        ctx: &ExecutionContext,
        records: &[Record],
    ) -> ExecutionResult<CypherValue> {
        match expr {
            Expression::CountAll => {
                Ok(CypherValue::Integer(records.len() as i64))
            }
            Expression::FunctionCall(call) if self.functions.is_aggregate(&call.name) => {
                let extra_args: Vec<CypherValue> = if call.arguments.len() > 1 {
                    call.arguments[1..]
                        .iter()
                        .map(|arg| evaluator.evaluate(arg, ctx))
                        .collect::<ExecutionResult<Vec<_>>>()?
                } else {
                    vec![]
                };

                let mut acc = create_accumulator(&call.full_name(), call.distinct, &extra_args)?;

                for record in records {
                    let mut local_ctx = ctx.clone();
                    local_ctx.load_record(record);

                    let value = if call.arguments.is_empty() {
                        CypherValue::Integer(1)
                    } else {
                        evaluator.evaluate(&call.arguments[0], &local_ctx)?
                    };

                    acc.accumulate(value)?;
                }

                Ok(acc.finalize())
            }
            _ => {
                // Non-aggregate expression - evaluate with first record
                if let Some(record) = records.first() {
                    let mut local_ctx = ctx.clone();
                    local_ctx.load_record(record);
                    evaluator.evaluate(expr, &local_ctx)
                } else {
                    Ok(CypherValue::Null)
                }
            }
        }
    }

    /// Execute a RETURN projection in read-only mode
    fn execute_return_readonly(
        &self,
        projection: &Projection,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<(Vec<String>, Vec<Record>)> {
        let evaluator = Evaluator::new(self.graph, self.functions);
        let mut columns = Vec::new();
        let mut rows = Vec::new();

        // Determine columns
        match &projection.items {
            ProjectionItems::All => {
                if let Some(first) = current.records.first() {
                    columns = first.columns().to_vec();
                }
            }
            ProjectionItems::Explicit(items) => {
                for item in items {
                    let alias = item.alias.clone().unwrap_or_else(|| format!("{:?}", item.expression));
                    columns.push(alias);
                }
            }
        }

        // Project rows
        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);

            let mut new_record = Record::new();

            match &projection.items {
                ProjectionItems::All => {
                    new_record = record.clone();
                }
                ProjectionItems::Explicit(items) => {
                    for (item, col) in items.iter().zip(&columns) {
                        let value = evaluator.evaluate(&item.expression, &local_ctx)?;
                        new_record.add(col.clone(), value);
                    }
                }
            }

            rows.push(new_record);
        }

        Ok((columns, rows))
    }
}
