use pest::iterators::Pair;

use masl_ast::document::{
    AliasDecl, Expr, GrantDef, Item, RelationDef, ResourceDecl, SubjectDecl, TypeRef,
};
use masl_ast::{Document, Ident};
use masl_diagnostics::diagnostic::Diagnostic;
use masl_diagnostics::span::Span;

use crate::grammar::Rule;

pub fn pest_span_to_span(pest_span: &pest::Span<'_>) -> Span {
    let (line, col) = pest_span.start_pos().line_col();
    Span::new(pest_span.start(), pest_span.end(), line, col)
}

pub fn lower_ident(pair: Pair<'_, Rule>) -> Ident {
    let span = pest_span_to_span(&pair.as_span());
    Ident::new(pair.as_str(), span)
}

pub fn lower_document(pair: Pair<'_, Rule>) -> Result<Document, Diagnostic> {
    let mut inner = pair.into_inner();

    // namespace declaration
    let ns_pair = inner.next().unwrap();
    let ns_ident = lower_ident(ns_pair.into_inner().next().unwrap());

    // schema version declaration
    let schema_pair = inner.next().unwrap();
    let schema_span = pest_span_to_span(&schema_pair.as_span());
    let version_str = schema_pair.into_inner().next().unwrap().as_str();
    let schema_version = version_str.parse::<u32>().map_err(|e| {
        Diagnostic::error(
            "E0100",
            format!("Invalid schema version integer: {}", e),
            schema_span,
        )
    })?;

    let mut items = Vec::new();
    for item_pair in inner {
        if item_pair.as_rule() == Rule::EOI {
            break;
        }
        let item = match item_pair.as_rule() {
            Rule::subject_decl => Item::Subject(lower_subject(item_pair)?),
            Rule::resource_decl => Item::Resource(lower_resource(item_pair)?),
            Rule::alias_decl => Item::Alias(lower_alias(item_pair)?),
            r => {
                return Err(Diagnostic::error(
                    "E0100",
                    format!("Unexpected top-level element: {:?}", r),
                    pest_span_to_span(&item_pair.as_span()),
                ));
            }
        };
        items.push(item);
    }

    Ok(Document {
        namespace: ns_ident,
        schema_version,
        schema_span,
        items,
    })
}

pub fn lower_subject(pair: Pair<'_, Rule>) -> Result<SubjectDecl, Diagnostic> {
    let span = pest_span_to_span(&pair.as_span());
    let mut inner = pair.into_inner();
    let name = lower_ident(inner.next().unwrap());

    let mut relations = Vec::new();
    if let Some(body_pair) = inner.next()
        && body_pair.as_rule() == Rule::subject_body
    {
        let rel_block = body_pair.into_inner().next().unwrap();
        relations = lower_relation_block(rel_block)?;
    }

    Ok(SubjectDecl {
        name,
        relations,
        span,
    })
}

pub fn lower_resource(pair: Pair<'_, Rule>) -> Result<ResourceDecl, Diagnostic> {
    let span = pest_span_to_span(&pair.as_span());
    let mut inner = pair.into_inner();
    let name = lower_ident(inner.next().unwrap());

    let mut relations = Vec::new();
    let mut grants = Vec::new();

    for block in inner {
        match block.as_rule() {
            Rule::relation_block => {
                relations = lower_relation_block(block)?;
            }
            Rule::grant_block => {
                grants = lower_grant_block(block)?;
            }
            _ => {}
        }
    }

    Ok(ResourceDecl {
        name,
        relations,
        grants,
        span,
    })
}

pub fn lower_relation_block(pair: Pair<'_, Rule>) -> Result<Vec<RelationDef>, Diagnostic> {
    let mut defs = Vec::new();
    for inner in pair.into_inner() {
        if inner.as_rule() == Rule::relation_def {
            let span = pest_span_to_span(&inner.as_span());
            let mut parts = inner.into_inner();
            let name = lower_ident(parts.next().unwrap());
            let types = lower_type_expr(parts.next().unwrap())?;
            defs.push(RelationDef { name, types, span });
        }
    }
    Ok(defs)
}

pub fn lower_type_expr(pair: Pair<'_, Rule>) -> Result<Vec<TypeRef>, Diagnostic> {
    let mut refs = Vec::new();
    for ref_pair in pair.into_inner() {
        if ref_pair.as_rule() == Rule::type_ref {
            let span = pest_span_to_span(&ref_pair.as_span());
            let mut parts = ref_pair.into_inner();
            let name = lower_ident(parts.next().unwrap());
            let relation = parts.next().map(lower_ident);
            refs.push(TypeRef {
                name,
                relation,
                span,
            });
        }
    }
    Ok(refs)
}

pub fn lower_grant_block(pair: Pair<'_, Rule>) -> Result<Vec<GrantDef>, Diagnostic> {
    let mut defs = Vec::new();
    for inner in pair.into_inner() {
        if inner.as_rule() == Rule::grant_def {
            let span = pest_span_to_span(&inner.as_span());
            let mut parts = inner.into_inner();
            let name = lower_ident(parts.next().unwrap());
            let expression = lower_expr(parts.next().unwrap())?;
            defs.push(GrantDef {
                name,
                expression,
                span,
            });
        }
    }
    Ok(defs)
}

pub fn lower_alias(pair: Pair<'_, Rule>) -> Result<AliasDecl, Diagnostic> {
    let span = pest_span_to_span(&pair.as_span());
    let mut inner = pair.into_inner();
    let name = lower_ident(inner.next().unwrap());
    let expression = lower_expr(inner.next().unwrap())?;
    Ok(AliasDecl {
        name,
        expression,
        span,
    })
}

pub fn lower_expr(pair: Pair<'_, Rule>) -> Result<Expr, Diagnostic> {
    let span = pest_span_to_span(&pair.as_span());
    match pair.as_rule() {
        Rule::expr => {
            let mut inners = pair.into_inner();
            let first = lower_expr(inners.next().unwrap())?;
            let mut union_operands = vec![first];
            for next_pair in inners {
                union_operands.push(lower_expr(next_pair)?);
            }
            if union_operands.len() == 1 {
                Ok(union_operands.remove(0))
            } else {
                Ok(Expr::Union {
                    operands: union_operands,
                    span,
                })
            }
        }
        Rule::expr_intersection => {
            let mut inners = pair.into_inner();
            let first = lower_expr(inners.next().unwrap())?;
            let mut inter_operands = vec![first];
            for next_pair in inners {
                inter_operands.push(lower_expr(next_pair)?);
            }
            if inter_operands.len() == 1 {
                Ok(inter_operands.remove(0))
            } else {
                Ok(Expr::Intersection {
                    operands: inter_operands,
                    span,
                })
            }
        }
        Rule::expr_difference => {
            let mut inners = pair.into_inner();
            let first = lower_expr(inners.next().unwrap())?;
            let mut current = first;
            for right_pair in inners {
                let right_expr = lower_expr(right_pair)?;
                current = Expr::Difference {
                    left: Box::new(current),
                    right: Box::new(right_expr),
                    span,
                };
            }
            Ok(current)
        }
        Rule::expr_unary => {
            let s = pair.as_str().trim();
            if s.starts_with('!') {
                let operand = lower_expr(pair.into_inner().next().unwrap())?;
                Ok(Expr::Not {
                    operand: Box::new(operand),
                    span,
                })
            } else {
                lower_expr(pair.into_inner().next().unwrap())
            }
        }
        Rule::traversal => {
            let steps = pair.into_inner().map(lower_ident).collect();
            Ok(Expr::Traversal { steps, span })
        }
        Rule::ident_lower => Ok(Expr::Relation(lower_ident(pair))),
        r => Err(Diagnostic::error(
            "E0100",
            format!("Invalid expression rule: {:?}", r),
            span,
        )),
    }
}
