use crate::model::{
    AssignKind, ConstantKind, ControlFlowKind, Diagnostic, ExceptHandler, Expression,
    ExpressionKind, FixHint, FixKind, FunctionDef, GlobalBinding, IgnoreDirective, KeywordArg,
    Module, Parameter, ParameterKind, RuleId, Severity, SourceLocation, SourceSpan, Statement,
    StatementKind, WithItem,
};
use rustpython_parser::ast::{self, Constant, Ranged};
use rustpython_parser::text_size::{TextRange, TextSize};
use rustpython_parser::Parse;
use std::collections::BTreeSet;

pub trait ParserAdapter {
    fn parse_module(&self, source: &str, path: &str, ignore_decorator_name: &str) -> ParseOutcome;
}

#[derive(Debug, Clone)]
pub struct ParseOutcome {
    pub module: Module,
    pub diagnostics: Vec<Diagnostic>,
}

#[derive(Debug, Default, Clone, Copy)]
pub struct RustPythonParser;

impl ParserAdapter for RustPythonParser {
    fn parse_module(&self, source: &str, path: &str, ignore_decorator_name: &str) -> ParseOutcome {
        let line_index = LineIndex::new(source);
        match ast::Suite::parse(source, path) {
            Ok(suite) => {
                let mut builder = ModuleBuilder::new(path, &line_index, ignore_decorator_name);
                let body = builder.convert_block(suite);
                let globals = collect_global_bindings(&body);
                ParseOutcome {
                    module: Module {
                        path: path.to_owned(),
                        body,
                        functions: builder.functions,
                        globals,
                    },
                    diagnostics: Vec::new(),
                }
            }
            Err(error) => {
                let location = line_index.location(error.offset);
                let span = SourceSpan {
                    path: path.to_owned(),
                    start: location.clone(),
                    end: Some(location),
                };
                ParseOutcome {
                    module: Module {
                        path: path.to_owned(),
                        body: Vec::new(),
                        functions: Vec::new(),
                        globals: Vec::new(),
                    },
                    diagnostics: vec![Diagnostic::new(
                        RuleId::ParseError,
                        Severity::Error,
                        error.to_string(),
                        None,
                        span,
                        Some("構文エラーを解消してから再実行してください。".to_owned()),
                        Some(FixHint {
                            kind: FixKind::ManualReview,
                            summary: "構文エラーを修正してください。".to_owned(),
                            suggested_action:
                                "Python 構文が正しい状態に直してから再実行してください。".to_owned(),
                        }),
                    )],
                }
            }
        }
    }
}

struct ModuleBuilder<'a> {
    path: &'a str,
    line_index: &'a LineIndex,
    ignore_decorator_name: &'a str,
    scope: Vec<String>,
    class_scope: Vec<String>,
    functions: Vec<FunctionDef>,
}

struct FunctionParts {
    name: String,
    arguments: ast::Arguments,
    body: Vec<ast::Stmt>,
    decorators: Vec<ast::Expr>,
    returns: Option<Expression>,
    is_async: bool,
    range: TextRange,
}

impl<'a> ModuleBuilder<'a> {
    fn new(path: &'a str, line_index: &'a LineIndex, ignore_decorator_name: &'a str) -> Self {
        Self {
            path,
            line_index,
            ignore_decorator_name,
            scope: Vec::new(),
            class_scope: Vec::new(),
            functions: Vec::new(),
        }
    }

    fn convert_block(&mut self, statements: Vec<ast::Stmt>) -> Vec<Statement> {
        statements
            .into_iter()
            .map(|statement| self.convert_statement(statement))
            .collect()
    }

    fn convert_statement(&mut self, statement: ast::Stmt) -> Statement {
        let location = self.span(statement.range());
        let kind = match statement {
            ast::Stmt::FunctionDef(node) => {
                let returns = node.returns.map(|expr| self.convert_expression(*expr));
                let function = self.convert_function(FunctionParts {
                    name: node.name.to_string(),
                    arguments: *node.args,
                    body: node.body,
                    decorators: node.decorator_list,
                    returns,
                    is_async: false,
                    range: node.range,
                });
                StatementKind::FunctionDef(Box::new(function))
            }
            ast::Stmt::AsyncFunctionDef(node) => {
                let returns = node.returns.map(|expr| self.convert_expression(*expr));
                let function = self.convert_function(FunctionParts {
                    name: node.name.to_string(),
                    arguments: *node.args,
                    body: node.body,
                    decorators: node.decorator_list,
                    returns,
                    is_async: true,
                    range: node.range,
                });
                StatementKind::FunctionDef(Box::new(function))
            }
            ast::Stmt::ClassDef(node) => {
                let name = node.name.to_string();
                self.scope.push(name.clone());
                self.class_scope.push(name.clone());
                let body = self.convert_block(node.body);
                let _ = self.class_scope.pop();
                let _ = self.scope.pop();
                StatementKind::ClassDef {
                    name,
                    bases: node
                        .bases
                        .into_iter()
                        .map(|expr| self.convert_expression(expr))
                        .collect(),
                    decorators: node
                        .decorator_list
                        .into_iter()
                        .map(|expr| self.convert_expression(expr))
                        .collect(),
                    body,
                }
            }
            ast::Stmt::Return(node) => StatementKind::Return {
                value: node.value.map(|expr| self.convert_expression(*expr)),
            },
            ast::Stmt::Delete(node) => StatementKind::Delete {
                targets: node
                    .targets
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
            },
            ast::Stmt::Assign(node) => StatementKind::Assign {
                targets: node
                    .targets
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
                value: Some(self.convert_expression(*node.value)),
                annotation: None,
                kind: AssignKind::Simple,
            },
            ast::Stmt::AnnAssign(node) => StatementKind::Assign {
                targets: vec![self.convert_expression(*node.target)],
                value: node.value.map(|expr| self.convert_expression(*expr)),
                annotation: Some(self.convert_expression(*node.annotation)),
                kind: AssignKind::Annotated,
            },
            ast::Stmt::AugAssign(node) => StatementKind::Assign {
                targets: vec![self.convert_expression(*node.target)],
                value: Some(self.convert_expression(*node.value)),
                annotation: None,
                kind: AssignKind::Augmented,
            },
            ast::Stmt::For(node) => StatementKind::For {
                target: self.convert_expression(*node.target),
                iter: self.convert_expression(*node.iter),
                body: self.convert_block(node.body),
                orelse: self.convert_block(node.orelse),
                is_async: false,
            },
            ast::Stmt::AsyncFor(node) => StatementKind::For {
                target: self.convert_expression(*node.target),
                iter: self.convert_expression(*node.iter),
                body: self.convert_block(node.body),
                orelse: self.convert_block(node.orelse),
                is_async: true,
            },
            ast::Stmt::While(node) => StatementKind::While {
                test: self.convert_expression(*node.test),
                body: self.convert_block(node.body),
                orelse: self.convert_block(node.orelse),
            },
            ast::Stmt::If(node) => StatementKind::If {
                test: self.convert_expression(*node.test),
                body: self.convert_block(node.body),
                orelse: self.convert_block(node.orelse),
            },
            ast::Stmt::With(node) => StatementKind::With {
                items: node
                    .items
                    .into_iter()
                    .map(|item| self.convert_with_item(item))
                    .collect(),
                body: self.convert_block(node.body),
                is_async: false,
            },
            ast::Stmt::AsyncWith(node) => StatementKind::With {
                items: node
                    .items
                    .into_iter()
                    .map(|item| self.convert_with_item(item))
                    .collect(),
                body: self.convert_block(node.body),
                is_async: true,
            },
            ast::Stmt::Try(node) => StatementKind::Try {
                body: self.convert_block(node.body),
                handlers: node
                    .handlers
                    .into_iter()
                    .map(|handler| self.convert_handler(handler))
                    .collect(),
                orelse: self.convert_block(node.orelse),
                finalbody: self.convert_block(node.finalbody),
            },
            ast::Stmt::TryStar(node) => StatementKind::Try {
                body: self.convert_block(node.body),
                handlers: node
                    .handlers
                    .into_iter()
                    .map(|handler| self.convert_handler(handler))
                    .collect(),
                orelse: self.convert_block(node.orelse),
                finalbody: self.convert_block(node.finalbody),
            },
            ast::Stmt::Raise(node) => StatementKind::Raise {
                exc: node.exc.map(|expr| self.convert_expression(*expr)),
                cause: node.cause.map(|expr| self.convert_expression(*expr)),
            },
            ast::Stmt::Assert(node) => StatementKind::Assert {
                test: self.convert_expression(*node.test),
                message: node.msg.map(|expr| self.convert_expression(*expr)),
            },
            ast::Stmt::Import(node) => StatementKind::Import {
                module: None,
                names: node.names.into_iter().map(alias_name).collect(),
            },
            ast::Stmt::ImportFrom(node) => StatementKind::Import {
                module: node.module.map(|module| module.to_string()),
                names: node.names.into_iter().map(alias_name).collect(),
            },
            ast::Stmt::Global(node) => StatementKind::Global {
                names: node
                    .names
                    .into_iter()
                    .map(|name| name.to_string())
                    .collect(),
            },
            ast::Stmt::Nonlocal(node) => StatementKind::Nonlocal {
                names: node
                    .names
                    .into_iter()
                    .map(|name| name.to_string())
                    .collect(),
            },
            ast::Stmt::Expr(node) => StatementKind::Expression {
                expression: self.convert_expression(*node.value),
            },
            ast::Stmt::Pass(_) => StatementKind::ControlFlow {
                kind: ControlFlowKind::Pass,
            },
            ast::Stmt::Break(_) => StatementKind::ControlFlow {
                kind: ControlFlowKind::Break,
            },
            ast::Stmt::Continue(_) => StatementKind::ControlFlow {
                kind: ControlFlowKind::Continue,
            },
            other => StatementKind::Other {
                label: format!("{other:?}"),
            },
        };

        Statement { kind, location }
    }

    fn convert_function(&mut self, parts: FunctionParts) -> FunctionDef {
        let qualified_name = self.qualified_name(&parts.name);
        let decorator_expressions = parts
            .decorators
            .into_iter()
            .map(|expr| self.convert_expression(expr))
            .collect::<Vec<_>>();
        let ignore = decorator_expressions.iter().find_map(|expr| {
            let target = match &expr.kind {
                ExpressionKind::Call { function, .. } => function.as_ref(),
                _ => expr,
            };

            target
                .last_segment()
                .filter(|segment| segment == self.ignore_decorator_name)
                .map(|decorator_name| IgnoreDirective {
                    decorator_name,
                    location: expr.location.clone(),
                })
        });

        self.scope.push(parts.name.clone());
        let body = self.convert_block(parts.body);
        let _ = self.scope.pop();

        let mut function = FunctionDef {
            name: parts.name,
            qualified_name,
            class_name: self
                .class_scope
                .last()
                .cloned()
                .filter(|class_name| self.scope.last() == Some(class_name)),
            is_async: parts.is_async,
            parameters: self.convert_arguments(parts.arguments),
            returns: parts.returns,
            decorators: decorator_expressions,
            body,
            location: self.span(parts.range),
            ignore,
            local_bindings: Vec::new(),
        };
        function.local_bindings = collect_local_bindings(&function).into_iter().collect();
        self.functions.push(function.clone());
        function
    }

    fn convert_arguments(&mut self, arguments: ast::Arguments) -> Vec<Parameter> {
        let mut parameters = Vec::new();

        for argument in arguments.posonlyargs {
            parameters.push(self.convert_parameter(argument, ParameterKind::PositionalOnly));
        }
        for argument in arguments.args {
            parameters.push(self.convert_parameter(argument, ParameterKind::Positional));
        }
        if let Some(vararg) = arguments.vararg {
            parameters.push(Parameter {
                name: vararg.arg.to_string(),
                kind: ParameterKind::VarArg,
                annotation: vararg.annotation.map(|expr| self.convert_expression(*expr)),
                default: None,
                location: self.span(vararg.range),
            });
        }
        for argument in arguments.kwonlyargs {
            parameters.push(self.convert_parameter(argument, ParameterKind::KeywordOnly));
        }
        if let Some(kwarg) = arguments.kwarg {
            parameters.push(Parameter {
                name: kwarg.arg.to_string(),
                kind: ParameterKind::KwArg,
                annotation: kwarg.annotation.map(|expr| self.convert_expression(*expr)),
                default: None,
                location: self.span(kwarg.range),
            });
        }

        parameters
    }

    fn convert_parameter(
        &mut self,
        argument: ast::ArgWithDefault,
        kind: ParameterKind,
    ) -> Parameter {
        Parameter {
            name: argument.def.arg.to_string(),
            kind,
            annotation: argument
                .def
                .annotation
                .map(|expr| self.convert_expression(*expr)),
            default: argument.default.map(|expr| self.convert_expression(*expr)),
            location: self.span(argument.def.range),
        }
    }

    fn convert_with_item(&mut self, item: ast::WithItem) -> WithItem {
        WithItem {
            context_expr: self.convert_expression(item.context_expr),
            optional_vars: item
                .optional_vars
                .map(|expr| self.convert_expression(*expr)),
        }
    }

    fn convert_handler(&mut self, handler: ast::ExceptHandler) -> ExceptHandler {
        match handler {
            ast::ExceptHandler::ExceptHandler(handler) => ExceptHandler {
                type_expr: handler.type_.map(|expr| self.convert_expression(*expr)),
                name: handler.name.map(|name| name.to_string()),
                body: self.convert_block(handler.body),
                location: self.span(handler.range),
            },
        }
    }

    fn convert_expression(&mut self, expression: ast::Expr) -> Expression {
        let location = self.span(expression.range());
        let kind = match expression {
            ast::Expr::Name(node) => ExpressionKind::Name {
                id: node.id.to_string(),
            },
            ast::Expr::Attribute(node) => ExpressionKind::Attribute {
                value: Box::new(self.convert_expression(*node.value)),
                attr: node.attr.to_string(),
            },
            ast::Expr::Call(node) => ExpressionKind::Call {
                function: Box::new(self.convert_expression(*node.func)),
                args: node
                    .args
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
                keywords: node
                    .keywords
                    .into_iter()
                    .map(|keyword| KeywordArg {
                        name: keyword.arg.map(|name| name.to_string()),
                        value: self.convert_expression(keyword.value),
                    })
                    .collect(),
            },
            ast::Expr::Constant(node) => ExpressionKind::Constant {
                kind: constant_kind(&node.value),
            },
            ast::Expr::List(node) => ExpressionKind::List {
                elements: node
                    .elts
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
            },
            ast::Expr::Tuple(node) => ExpressionKind::Tuple {
                elements: node
                    .elts
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
            },
            ast::Expr::Set(node) => ExpressionKind::Set {
                elements: node
                    .elts
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
            },
            ast::Expr::Dict(node) => ExpressionKind::Dict {
                entries: node
                    .keys
                    .into_iter()
                    .zip(node.values)
                    .map(|(key, value)| crate::model::DictEntry {
                        key: key.map(|expr| self.convert_expression(expr)),
                        value: self.convert_expression(value),
                    })
                    .collect(),
            },
            ast::Expr::BinOp(node) => ExpressionKind::Binary {
                left: Box::new(self.convert_expression(*node.left)),
                operator: format!("{:?}", node.op),
                right: Box::new(self.convert_expression(*node.right)),
            },
            ast::Expr::UnaryOp(node) => ExpressionKind::Unary {
                operator: format!("{:?}", node.op),
                operand: Box::new(self.convert_expression(*node.operand)),
            },
            ast::Expr::BoolOp(node) => ExpressionKind::BoolOp {
                operator: format!("{:?}", node.op),
                values: node
                    .values
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
            },
            ast::Expr::Compare(node) => ExpressionKind::Compare {
                left: Box::new(self.convert_expression(*node.left)),
                operators: node.ops.into_iter().map(|op| format!("{op:?}")).collect(),
                comparators: node
                    .comparators
                    .into_iter()
                    .map(|expr| self.convert_expression(expr))
                    .collect(),
            },
            ast::Expr::IfExp(node) => ExpressionKind::IfElse {
                test: Box::new(self.convert_expression(*node.test)),
                body: Box::new(self.convert_expression(*node.body)),
                orelse: Box::new(self.convert_expression(*node.orelse)),
            },
            ast::Expr::Lambda(_) => ExpressionKind::Lambda,
            ast::Expr::Subscript(node) => ExpressionKind::Subscript {
                value: Box::new(self.convert_expression(*node.value)),
                slice: Box::new(self.convert_expression(*node.slice)),
            },
            ast::Expr::Await(node) => ExpressionKind::Await {
                value: Box::new(self.convert_expression(*node.value)),
            },
            ast::Expr::Yield(node) => ExpressionKind::Yield {
                value: node
                    .value
                    .map(|expr| Box::new(self.convert_expression(*expr))),
            },
            ast::Expr::YieldFrom(node) => ExpressionKind::YieldFrom {
                value: Box::new(self.convert_expression(*node.value)),
            },
            ast::Expr::NamedExpr(node) => ExpressionKind::Named {
                target: Box::new(self.convert_expression(*node.target)),
                value: Box::new(self.convert_expression(*node.value)),
            },
            ast::Expr::Starred(node) => ExpressionKind::Starred {
                value: Box::new(self.convert_expression(*node.value)),
            },
            ast::Expr::Slice(node) => ExpressionKind::Slice {
                lower: node
                    .lower
                    .map(|expr| Box::new(self.convert_expression(*expr))),
                upper: node
                    .upper
                    .map(|expr| Box::new(self.convert_expression(*expr))),
                step: node
                    .step
                    .map(|expr| Box::new(self.convert_expression(*expr))),
            },
            other => ExpressionKind::Other {
                label: format!("{other:?}"),
            },
        };

        Expression { kind, location }
    }

    fn qualified_name(&self, name: &str) -> String {
        if self.scope.is_empty() {
            name.to_owned()
        } else {
            format!("{}.{}", self.scope.join("."), name)
        }
    }

    fn span(&self, range: TextRange) -> SourceSpan {
        self.line_index.span(self.path, range)
    }
}

#[derive(Debug)]
struct LineIndex {
    starts: Vec<usize>,
}

impl LineIndex {
    fn new(source: &str) -> Self {
        let mut starts = vec![0];
        for (index, character) in source.char_indices() {
            if character == '\n' {
                starts.push(index + 1);
            }
        }
        Self { starts }
    }

    fn location(&self, offset: TextSize) -> SourceLocation {
        let offset = usize::try_from(u32::from(offset)).unwrap_or(0);
        let line_index = match self.starts.binary_search(&offset) {
            Ok(index) => index,
            Err(index) => index.saturating_sub(1),
        };
        let line_start = self.starts.get(line_index).copied().unwrap_or(0);
        SourceLocation {
            line: line_index + 1,
            column: offset.saturating_sub(line_start) + 1,
            offset,
        }
    }

    fn span(&self, path: &str, range: TextRange) -> SourceSpan {
        SourceSpan {
            path: path.to_owned(),
            start: self.location(range.start()),
            end: Some(self.location(range.end())),
        }
    }
}

fn constant_kind(value: &Constant) -> ConstantKind {
    match value {
        Constant::None => ConstantKind::None,
        Constant::Bool(_) => ConstantKind::Bool,
        Constant::Str(_) => ConstantKind::String,
        Constant::Bytes(_) => ConstantKind::Bytes,
        Constant::Int(_) => ConstantKind::Integer,
        Constant::Tuple(_) => ConstantKind::Tuple,
        Constant::Float(_) => ConstantKind::Float,
        Constant::Complex { .. } => ConstantKind::Complex,
        Constant::Ellipsis => ConstantKind::Ellipsis,
    }
}

fn alias_name(alias: ast::Alias) -> String {
    alias
        .asname
        .map_or_else(|| alias.name.to_string(), |name| name.to_string())
}

fn collect_global_bindings(body: &[Statement]) -> Vec<GlobalBinding> {
    let mut bindings = Vec::new();
    for statement in body {
        match &statement.kind {
            StatementKind::Assign {
                targets,
                value,
                kind: AssignKind::Simple | AssignKind::Annotated,
                ..
            } => {
                let is_mutable = value.as_ref().is_some_and(Expression::is_mutable_value);
                for name in extract_target_names(targets) {
                    bindings.push(GlobalBinding {
                        name,
                        is_mutable,
                        location: statement.location.clone(),
                    });
                }
            }
            StatementKind::Import { names, .. } => {
                for name in names {
                    bindings.push(GlobalBinding {
                        name: name.clone(),
                        is_mutable: false,
                        location: statement.location.clone(),
                    });
                }
            }
            StatementKind::FunctionDef(function) => bindings.push(GlobalBinding {
                name: function.name.clone(),
                is_mutable: false,
                location: function.location.clone(),
            }),
            StatementKind::ClassDef { name, .. } => bindings.push(GlobalBinding {
                name: name.clone(),
                is_mutable: false,
                location: statement.location.clone(),
            }),
            _ => {}
        }
    }
    bindings
}

fn collect_local_bindings(function: &FunctionDef) -> BTreeSet<String> {
    let mut names = function
        .parameters
        .iter()
        .map(|parameter| parameter.name.clone())
        .collect::<BTreeSet<_>>();
    collect_block_bindings(&function.body, &mut names);
    names
}

fn collect_block_bindings(statements: &[Statement], names: &mut BTreeSet<String>) {
    for statement in statements {
        match &statement.kind {
            StatementKind::FunctionDef(function) => {
                names.insert(function.name.clone());
            }
            StatementKind::ClassDef { name, .. } => {
                names.insert(name.clone());
            }
            StatementKind::Assign { targets, .. } => {
                names.extend(extract_target_names(targets));
            }
            StatementKind::For {
                target,
                body,
                orelse,
                ..
            } => {
                names.extend(extract_name_from_expression(target));
                collect_block_bindings(body, names);
                collect_block_bindings(orelse, names);
            }
            StatementKind::While { body, orelse, .. } | StatementKind::If { body, orelse, .. } => {
                collect_block_bindings(body, names);
                collect_block_bindings(orelse, names);
            }
            StatementKind::With { items, body, .. } => {
                for item in items {
                    if let Some(optional_vars) = &item.optional_vars {
                        names.extend(extract_name_from_expression(optional_vars));
                    }
                }
                collect_block_bindings(body, names);
            }
            StatementKind::Try {
                body,
                handlers,
                orelse,
                finalbody,
            } => {
                collect_block_bindings(body, names);
                collect_block_bindings(orelse, names);
                collect_block_bindings(finalbody, names);
                for handler in handlers {
                    if let Some(name) = &handler.name {
                        names.insert(name.clone());
                    }
                    collect_block_bindings(&handler.body, names);
                }
            }
            StatementKind::Import {
                names: imported, ..
            } => {
                names.extend(imported.iter().cloned());
            }
            _ => {}
        }
    }
}

fn extract_target_names(targets: &[Expression]) -> BTreeSet<String> {
    let mut names = BTreeSet::new();
    for target in targets {
        names.extend(extract_name_from_expression(target));
    }
    names
}

fn extract_name_from_expression(expression: &Expression) -> BTreeSet<String> {
    let mut names = BTreeSet::new();
    match &expression.kind {
        ExpressionKind::Name { id } => {
            names.insert(id.clone());
        }
        ExpressionKind::Tuple { elements }
        | ExpressionKind::List { elements }
        | ExpressionKind::Set { elements } => {
            for element in elements {
                names.extend(extract_name_from_expression(element));
            }
        }
        ExpressionKind::Starred { value } => {
            names.extend(extract_name_from_expression(value));
        }
        _ => {}
    }
    names
}
