use std::{
    collections::{HashSet, hash_map::DefaultHasher},
    fs,
    hash::{Hash, Hasher},
    path::{Path, PathBuf},
};

use crate::MigrationError;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct MigrationFile {
    pub path: PathBuf,
    pub id: u64,
    pub name: String,
    pub checksum: String,
    pub up: Vec<Statement>,
    pub down: Vec<Statement>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Statement {
    Sql {
        sql: String,
    },
    Backfill {
        sql: String,
    },
    CreateTable {
        name: String,
        items: Vec<TableItem>,
    },
    AlterTable {
        name: String,
        actions: Vec<AlterAction>,
    },
    DropTable {
        name: String,
    },
    RenameTable {
        from: String,
        to: String,
    },
    CreateEnum {
        name: String,
        values: Vec<String>,
    },
    AlterEnum {
        name: String,
        actions: Vec<AlterEnumAction>,
    },
    DropEnum {
        name: String,
    },
    RenameEnum {
        from: String,
        to: String,
    },
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum TableItem {
    Column(Column),
    Index {
        name: Option<String>,
        columns: Vec<String>,
    },
    Unique(Vec<String>),
    Primary(Vec<String>),
    ConstraintUnique {
        name: String,
        columns: Vec<String>,
    },
    Timestamps,
    SoftDeletes,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Column {
    pub name: String,
    pub ty: ColumnType,
    pub nullable: Nullability,
    pub primary: bool,
    pub unique: bool,
    pub index: bool,
    pub default: Option<String>,
    pub reference: Option<Reference>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ColumnType {
    Implicit,
    Id,
    String,
    Text,
    Integer,
    BigInt,
    Boolean,
    Date,
    Time,
    DateTime,
    Timestamp,
    TimestampTz,
    Json,
    Uuid,
    RememberToken,
    Varchar(u32),
    Decimal(u32, u32),
    Float,
    Double,
    Custom(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Nullability {
    Implicit,
    Nullable,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Reference {
    pub table: String,
    pub column: Option<String>,
    pub on_delete: Option<ReferenceAction>,
    pub on_update: Option<ReferenceAction>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReferenceAction {
    Cascade,
    Restrict,
    SetNull,
    NoAction,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum AlterAction {
    AddColumn(Column),
    DropColumn(String),
    RenameColumn {
        from: String,
        to: String,
    },
    AddIndex {
        name: Option<String>,
        columns: Vec<String>,
    },
    DropIndex(String),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum AlterEnumAction {
    AddValue(String),
}

pub(crate) use self::{Column as Field, ColumnType as FieldType, Statement as MigrationOp};

pub(crate) fn default_migration_dir() -> PathBuf {
    PathBuf::from("migrations")
}

pub(crate) fn load_migrations(dir: &Path) -> Result<Vec<MigrationFile>, MigrationError> {
    let mut lift_migrations = Vec::new();
    let mut sql_migrations = std::collections::BTreeMap::<(u64, String), SqlMigrationParts>::new();
    let read_dir = fs::read_dir(dir).map_err(|source| MigrationError::MigrationDirectory {
        path: dir.to_path_buf(),
        source,
    })?;

    for entry in read_dir {
        let entry = entry.map_err(MigrationError::Io)?;
        let path = entry.path();
        let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else {
            continue;
        };

        if file_name.ends_with(".lift") {
            lift_migrations.push(parse_migration_file(&path)?);
            continue;
        }

        if file_name.ends_with(".sql") {
            let ParsedSqlFilename { id, name, kind } = parse_sql_filename(file_name, &path)?;
            let source = fs::read_to_string(&path).map_err(MigrationError::Io)?;
            let parts = sql_migrations.entry((id, name)).or_default();
            match kind {
                SqlFileKind::Up => {
                    if parts.up.replace((path.clone(), source)).is_some() {
                        return Err(MigrationError::InvalidMigrationFile {
                            path,
                            message: "duplicate forward sql migration".to_owned(),
                        });
                    }
                }
                SqlFileKind::Down => {
                    if parts.down.replace((path.clone(), source)).is_some() {
                        return Err(MigrationError::InvalidMigrationFile {
                            path,
                            message: "duplicate rollback sql migration".to_owned(),
                        });
                    }
                }
            }
        }
    }

    let mut migrations = lift_migrations;
    for ((id, name), parts) in sql_migrations {
        let (up_path, up_source) =
            parts
                .up
                .ok_or_else(|| MigrationError::InvalidMigrationFile {
                    path: dir.to_path_buf(),
                    message: format!("missing forward sql migration for `{id}_{name}`"),
                })?;
        let mut checksum_source = String::with_capacity(
            up_source.len()
                + parts
                    .down
                    .as_ref()
                    .map(|(_, down_source)| down_source.len() + "\n-- down --\n".len())
                    .unwrap_or(0),
        );
        checksum_source.push_str(&up_source);
        if let Some((_, down_source)) = &parts.down {
            checksum_source.push_str("\n-- down --\n");
            checksum_source.push_str(down_source);
        }
        let file_name = up_path
            .file_name()
            .and_then(|value| value.to_str())
            .ok_or_else(|| MigrationError::InvalidMigrationFilename(up_path.clone()))?;
        migrations.push(MigrationFile {
            path: up_path.clone(),
            id,
            name,
            checksum: checksum(file_name, &checksum_source),
            up: vec![Statement::Sql { sql: up_source }],
            down: parts
                .down
                .map(|(_, sql)| vec![Statement::Sql { sql }])
                .unwrap_or_default(),
        });
    }

    migrations.sort_by_key(|migration| migration.id);

    for pair in migrations.windows(2) {
        if pair[0].id == pair[1].id {
            return Err(MigrationError::DuplicateVersion(pair[0].id));
        }
    }

    Ok(migrations)
}

#[derive(Default)]
struct SqlMigrationParts {
    up: Option<(PathBuf, String)>,
    down: Option<(PathBuf, String)>,
}

struct ParsedSqlFilename {
    id: u64,
    name: String,
    kind: SqlFileKind,
}

type StatementLines = Vec<String>;
type SplitLines = (StatementLines, StatementLines);

enum SqlFileKind {
    Up,
    Down,
}

fn parse_migration_file(path: &Path) -> Result<MigrationFile, MigrationError> {
    let file_name = path
        .file_name()
        .and_then(|value| value.to_str())
        .ok_or_else(|| MigrationError::InvalidMigrationFilename(path.to_path_buf()))?;
    let (id, name) = parse_filename(file_name, path)?;
    let source = fs::read_to_string(path).map_err(MigrationError::Io)?;
    parse_source(path.to_path_buf(), id, name, file_name, &source)
}

fn parse_source(
    path: PathBuf,
    id: u64,
    name: String,
    file_name: &str,
    source: &str,
) -> Result<MigrationFile, MigrationError> {
    let checksum = checksum(file_name, source);
    let lines = normalized_lines(source);
    let (up_lines, down_lines) = split_top_level(&lines, &path)?;
    let up = parse_statements(&up_lines, &path)?;
    validate_statements(&up, &path, "forward")?;
    let down = parse_statements(&down_lines, &path)?;
    validate_statements(&down, &path, "down")?;

    Ok(MigrationFile {
        path,
        id,
        name,
        checksum,
        up,
        down,
    })
}

fn validate_statements(
    statements: &[Statement],
    path: &Path,
    direction: &str,
) -> Result<(), MigrationError> {
    let mut tables = HashSet::new();
    let mut enums = HashSet::new();

    for statement in statements {
        match statement {
            Statement::CreateTable { name, .. } => {
                if !tables.insert(name.as_str()) {
                    return Err(MigrationError::InvalidMigrationFile {
                        path: path.to_path_buf(),
                        message: format!(
                            "duplicate `create {name}` in {direction} migration statements"
                        ),
                    });
                }
            }
            Statement::CreateEnum { name, .. } => {
                if !enums.insert(name.as_str()) {
                    return Err(MigrationError::InvalidMigrationFile {
                        path: path.to_path_buf(),
                        message: format!(
                            "duplicate `create enum {name}` in {direction} migration statements"
                        ),
                    });
                }
            }
            _ => {}
        }
    }

    Ok(())
}

fn parse_filename(file_name: &str, path: &Path) -> Result<(u64, String), MigrationError> {
    let stem = file_name
        .strip_suffix(".lift")
        .ok_or_else(|| MigrationError::InvalidMigrationFilename(path.to_path_buf()))?;
    let (id, name) = stem
        .split_once('_')
        .ok_or_else(|| MigrationError::InvalidMigrationFilename(path.to_path_buf()))?;
    let id = id
        .parse::<u64>()
        .map_err(|_| MigrationError::InvalidMigrationFilename(path.to_path_buf()))?;
    if name.is_empty() {
        return Err(MigrationError::InvalidMigrationFilename(path.to_path_buf()));
    }
    Ok((id, name.to_owned()))
}

fn parse_sql_filename(file_name: &str, path: &Path) -> Result<ParsedSqlFilename, MigrationError> {
    let (stem, kind) = if let Some(stem) = file_name.strip_suffix(".down.sql") {
        (stem, SqlFileKind::Down)
    } else if let Some(stem) = file_name.strip_suffix(".sql") {
        (stem, SqlFileKind::Up)
    } else {
        return Err(MigrationError::InvalidMigrationFilename(path.to_path_buf()));
    };

    let (id, name) = stem
        .split_once('_')
        .ok_or_else(|| MigrationError::InvalidMigrationFilename(path.to_path_buf()))?;
    let id = id
        .parse::<u64>()
        .map_err(|_| MigrationError::InvalidMigrationFilename(path.to_path_buf()))?;
    if name.is_empty() {
        return Err(MigrationError::InvalidMigrationFilename(path.to_path_buf()));
    }
    Ok(ParsedSqlFilename {
        id,
        name: name.to_owned(),
        kind,
    })
}

fn checksum(file_name: &str, source: &str) -> String {
    let mut hasher = DefaultHasher::new();
    file_name.hash(&mut hasher);
    source.hash(&mut hasher);
    format!("{:016x}", hasher.finish())
}

fn normalized_lines(source: &str) -> Vec<String> {
    let mut lines = Vec::new();
    let mut in_sql = false;
    let mut in_backfill = false;

    for raw_line in source.lines() {
        if in_sql {
            let trimmed = raw_line.trim();
            if trimmed == r#"""""# {
                lines.push(r#"""""#.to_owned());
                in_sql = false;
            } else {
                lines.push(raw_line.to_owned());
            }
            continue;
        }
        if in_backfill {
            let trimmed = raw_line.trim();
            lines.push(raw_line.to_owned());
            if trimmed == "}" {
                in_backfill = false;
            }
            continue;
        }

        let line = normalize_line(strip_line_comment(raw_line).trim());
        if line.is_empty() {
            continue;
        }
        if line == r#"sql """"# {
            in_sql = true;
        } else if line == "backfill {" {
            in_backfill = true;
        }
        lines.push(line);
    }

    lines
}

fn normalize_line(line: &str) -> String {
    let mut normalized = line.trim();
    loop {
        let trimmed = normalized.trim_end();
        if let Some(stripped) = trimmed.strip_suffix(';') {
            normalized = stripped;
            continue;
        }
        return trimmed.to_owned();
    }
}

fn strip_line_comment(line: &str) -> &str {
    let mut in_string = false;
    let mut prev = '\0';
    for (index, ch) in line.char_indices() {
        match ch {
            '"' if prev != '\\' => in_string = !in_string,
            '/' if !in_string && prev == '/' => return &line[..index - 1],
            _ => {}
        }
        prev = ch;
    }
    line
}

fn split_top_level(lines: &[String], path: &Path) -> Result<SplitLines, MigrationError> {
    let mut up = Vec::new();
    let mut down = Vec::new();
    let mut index = 0usize;
    let mut saw_down = false;

    while index < lines.len() {
        let line = &lines[index];
        if line == "down {" {
            if saw_down {
                return Err(MigrationError::InvalidMigrationFile {
                    path: path.to_path_buf(),
                    message: "multiple `down` blocks are not allowed".to_owned(),
                });
            }
            let (body, next) = collect_body(lines, index + 1, path)?;
            down = body;
            saw_down = true;
            index = next;
            continue;
        }

        if saw_down {
            return Err(MigrationError::InvalidMigrationFile {
                path: path.to_path_buf(),
                message: "statements after `down` are not allowed".to_owned(),
            });
        }

        up.push(line.clone());
        index += 1;
    }

    Ok((up, down))
}

fn parse_statements(lines: &[String], path: &Path) -> Result<Vec<Statement>, MigrationError> {
    let mut index = 0usize;
    let mut statements = Vec::new();

    while index < lines.len() {
        let line = &lines[index];

        if line == r#"sql """"# {
            let (sql, next) = collect_sql_block(lines, index + 1, path)?;
            statements.push(Statement::Sql { sql });
            index = next;
            continue;
        }
        if line == "backfill {" {
            let (sql, next) = collect_backfill_block(lines, index + 1, path)?;
            if sql.trim().is_empty() {
                return Err(MigrationError::InvalidMigrationFile {
                    path: path.to_path_buf(),
                    message: "empty `backfill` blocks are not allowed".to_owned(),
                });
            }
            statements.push(Statement::Backfill { sql });
            index = next;
            continue;
        }

        if let Some(rest) = line.strip_prefix("create ") {
            if let Some(enum_rest) = rest.strip_prefix("enum ") {
                statements.push(parse_create_enum(enum_rest, path)?);
                index += 1;
                continue;
            }
            if let Some(name) = rest.strip_suffix(" {") {
                let name = parse_statement_identifier(name, path, "create")?;
                let (body, next) = collect_body(lines, index + 1, path)?;
                statements.push(Statement::CreateTable {
                    name,
                    items: parse_table_items(&body, path)?,
                });
                index = next;
                continue;
            }
        }

        if let Some(rest) = line.strip_prefix("alter ") {
            if let Some(name) = rest
                .strip_prefix("enum ")
                .and_then(|value| value.strip_suffix(" {"))
            {
                let (body, next) = collect_body(lines, index + 1, path)?;
                statements.push(Statement::AlterEnum {
                    name: name.to_owned(),
                    actions: parse_alter_enum_actions(&body, path)?,
                });
                index = next;
                continue;
            }
            if let Some(name) = rest.strip_suffix(" {") {
                let name = parse_statement_identifier(name, path, "alter")?;
                let (body, next) = collect_body(lines, index + 1, path)?;
                statements.push(Statement::AlterTable {
                    name,
                    actions: parse_alter_actions(&body, path)?,
                });
                index = next;
                continue;
            }
        }

        if let Some(rest) = line.strip_prefix("drop ") {
            if let Some(name) = rest.strip_prefix("enum ") {
                statements.push(Statement::DropEnum {
                    name: name.to_owned(),
                });
                index += 1;
                continue;
            }
            let name = parse_statement_identifier(rest, path, "drop")?;
            statements.push(Statement::DropTable { name });
            index += 1;
            continue;
        }

        if let Some(rest) = line.strip_prefix("rename ") {
            if let Some(enum_rest) = rest.strip_prefix("enum ") {
                let (from, to) = parse_rename(enum_rest, path, "enum")?;
                statements.push(Statement::RenameEnum { from, to });
                index += 1;
                continue;
            }
            let (from, to) = parse_rename(rest, path, "table")?;
            statements.push(Statement::RenameTable { from, to });
            index += 1;
            continue;
        }

        return Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("unsupported migration statement `{line}`"),
        });
    }

    Ok(statements)
}

fn parse_statement_identifier(
    value: &str,
    path: &Path,
    statement: &str,
) -> Result<String, MigrationError> {
    let name = value.trim();
    if is_identifier(name) {
        return Ok(name.to_owned());
    }

    Err(MigrationError::InvalidMigrationFile {
        path: path.to_path_buf(),
        message: format!("invalid `{statement}` statement `{value}`"),
    })
}

fn parse_create_enum(rest: &str, path: &Path) -> Result<Statement, MigrationError> {
    let (name, raw_values) = rest
        .split_once('{')
        .and_then(|(name, values)| values.strip_suffix('}').map(|values| (name.trim(), values)))
        .ok_or_else(|| MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("invalid enum declaration `create enum {rest}`"),
        })?;

    let values = raw_values
        .split(',')
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_owned)
        .collect::<Vec<_>>();

    if name.is_empty() || values.is_empty() {
        return Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("invalid enum declaration `create enum {rest}`"),
        });
    }

    Ok(Statement::CreateEnum {
        name: name.to_owned(),
        values,
    })
}

fn parse_alter_enum_actions(
    lines: &[String],
    path: &Path,
) -> Result<Vec<AlterEnumAction>, MigrationError> {
    let mut actions = Vec::new();
    for line in lines {
        if let Some(value) = line.strip_prefix("add value ") {
            actions.push(AlterEnumAction::AddValue(value.trim().to_owned()));
            continue;
        }
        return Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("unsupported alter enum action `{line}`"),
        });
    }
    Ok(actions)
}

fn parse_rename(rest: &str, path: &Path, kind: &str) -> Result<(String, String), MigrationError> {
    let (from, to) =
        rest.split_once(" to ")
            .ok_or_else(|| MigrationError::InvalidMigrationFile {
                path: path.to_path_buf(),
                message: format!("invalid rename {kind} statement `{rest}`"),
            })?;
    let from = from.trim();
    let to = to.trim();
    if !is_identifier(from) || !is_identifier(to) {
        return Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("invalid rename {kind} statement `{rest}`"),
        });
    }
    Ok((from.to_owned(), to.to_owned()))
}

fn collect_body(
    lines: &[String],
    mut index: usize,
    path: &Path,
) -> Result<(Vec<String>, usize), MigrationError> {
    let mut body = Vec::new();
    let mut depth = 1usize;

    while index < lines.len() {
        let line = &lines[index];
        let trimmed = line.trim();
        if trimmed == r#"sql """"# {
            body.push(line.clone());
            index += 1;
            while index < lines.len() {
                let sql_line = &lines[index];
                body.push(sql_line.clone());
                index += 1;
                if sql_line.trim() == r#"""""# {
                    break;
                }
            }
            continue;
        }
        if trimmed.ends_with('{') {
            depth += 1;
            body.push(line.clone());
            index += 1;
            continue;
        }
        if trimmed == "}" {
            depth -= 1;
            if depth == 0 {
                return Ok((body, index + 1));
            }
            body.push(line.clone());
            index += 1;
            continue;
        }
        body.push(line.clone());
        index += 1;
    }

    Err(MigrationError::InvalidMigrationFile {
        path: path.to_path_buf(),
        message: "unterminated statement block".to_owned(),
    })
}

fn collect_sql_block(
    lines: &[String],
    mut index: usize,
    path: &Path,
) -> Result<(String, usize), MigrationError> {
    let mut sql_lines = Vec::new();

    while index < lines.len() {
        let line = &lines[index];
        if line.trim() == r#"""""# {
            return Ok((sql_lines.join("\n"), index + 1));
        }
        sql_lines.push(line.clone());
        index += 1;
    }

    Err(MigrationError::InvalidMigrationFile {
        path: path.to_path_buf(),
        message: "unterminated sql block".to_owned(),
    })
}

fn collect_backfill_block(
    lines: &[String],
    mut index: usize,
    path: &Path,
) -> Result<(String, usize), MigrationError> {
    let mut sql_lines = Vec::new();

    while index < lines.len() {
        let line = &lines[index];
        if line.trim() == "}" {
            return Ok((sql_lines.join("\n"), index + 1));
        }
        sql_lines.push(line.clone());
        index += 1;
    }

    Err(MigrationError::InvalidMigrationFile {
        path: path.to_path_buf(),
        message: "unterminated `backfill` block".to_owned(),
    })
}

fn parse_table_items(lines: &[String], path: &Path) -> Result<Vec<TableItem>, MigrationError> {
    let mut items = Vec::new();

    for line in lines {
        if line == "timestamps" {
            items.push(TableItem::Timestamps);
            continue;
        }
        if line == "soft_deletes" {
            items.push(TableItem::SoftDeletes);
            continue;
        }
        if let Some(columns) = line.strip_prefix("index ") {
            items.push(parse_index(columns, path)?);
            continue;
        }
        if let Some(columns) = line.strip_prefix("unique ") {
            items.push(TableItem::Unique(parse_column_list(columns, path)?));
            continue;
        }
        if let Some(columns) = line.strip_prefix("primary ") {
            items.push(TableItem::Primary(parse_column_list(columns, path)?));
            continue;
        }
        if let Some(rest) = line.strip_prefix("constraint ") {
            items.push(parse_constraint(rest, path)?);
            continue;
        }
        items.push(TableItem::Column(parse_column(line, path)?));
    }

    Ok(items)
}

fn parse_index(value: &str, path: &Path) -> Result<TableItem, MigrationError> {
    let tokens = tokenize(value);
    match tokens.as_slice() {
        [columns] => Ok(TableItem::Index {
            name: None,
            columns: parse_column_list(columns, path)?,
        }),
        [name, columns] => Ok(TableItem::Index {
            name: Some(name.clone()),
            columns: parse_column_list(columns, path)?,
        }),
        _ => Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("invalid index declaration `index {value}`"),
        }),
    }
}

fn parse_constraint(value: &str, path: &Path) -> Result<TableItem, MigrationError> {
    let tokens = tokenize(value);
    match tokens.as_slice() {
        [name, kind, columns] if kind == "unique" => Ok(TableItem::ConstraintUnique {
            name: name.clone(),
            columns: parse_column_list(columns, path)?,
        }),
        _ => Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("invalid constraint declaration `constraint {value}`"),
        }),
    }
}

fn parse_alter_actions(lines: &[String], path: &Path) -> Result<Vec<AlterAction>, MigrationError> {
    let mut actions = Vec::new();

    for line in lines {
        if let Some(rest) = line.strip_prefix("add column ") {
            actions.push(AlterAction::AddColumn(parse_column(rest, path)?));
            continue;
        }
        if let Some(rest) = line.strip_prefix("drop column ") {
            actions.push(AlterAction::DropColumn(rest.trim().to_owned()));
            continue;
        }
        if let Some(rest) = line.strip_prefix("rename column ") {
            let (from, to) = parse_rename(rest, path, "column")?;
            actions.push(AlterAction::RenameColumn { from, to });
            continue;
        }
        if let Some(rest) = line.strip_prefix("add index ") {
            match parse_index(rest, path)? {
                TableItem::Index { name, columns } => {
                    actions.push(AlterAction::AddIndex { name, columns });
                }
                _ => unreachable!(),
            }
            continue;
        }
        if let Some(rest) = line.strip_prefix("drop index ") {
            actions.push(AlterAction::DropIndex(rest.trim().to_owned()));
            continue;
        }

        return Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("unsupported alter action `{line}`"),
        });
    }

    Ok(actions)
}

fn parse_column(line: &str, path: &Path) -> Result<Column, MigrationError> {
    let tokens = tokenize(line);
    if tokens.is_empty() {
        return Err(invalid_column(path, line, "missing column name"));
    }
    if tokens.len() == 1 {
        let name = tokens[0].clone();
        let ty = infer_implicit_column_type(&name)
            .ok_or_else(|| invalid_column(path, line, "missing column type"))?;
        return Ok(Column {
            name,
            ty,
            nullable: Nullability::Implicit,
            primary: false,
            unique: false,
            index: false,
            default: None,
            reference: None,
        });
    }

    let name = tokens[0].clone();
    let mut index = 1usize;
    let mut ty = ColumnType::Implicit;
    let mut primary = false;
    let mut unique = false;
    let mut nullable = Nullability::Implicit;
    let mut default = None;
    let mut reference = None;
    let indexed = false;
    let mut no_index = false;

    match tokens[index].as_str() {
        "primary" => {
            primary = true;
            index += 1;
        }
        "ref" => {
            let target = tokens.get(index + 1).ok_or_else(|| {
                invalid_column(path, line, "missing reference target after `ref`")
            })?;
            reference = Some(parse_reference_target(target));
            index += 2;
        }
        raw => {
            ty = parse_type(raw, path, line)?;
            index += 1;
            if let Some("ref") = tokens.get(index).map(String::as_str) {
                let target = tokens.get(index + 1).ok_or_else(|| {
                    invalid_column(path, line, "missing reference target after `ref`")
                })?;
                reference = Some(parse_reference_target(target));
                index += 2;
            }
        }
    }

    while index < tokens.len() {
        match tokens[index].as_str() {
            "primary" => {
                primary = true;
                index += 1;
            }
            "unique" => {
                unique = true;
                index += 1;
            }
            "nullable" => {
                nullable = Nullability::Nullable;
                index += 1;
            }
            "no" if tokens.get(index + 1).map(String::as_str) == Some("index") => {
                no_index = true;
                index += 2;
            }
            "default" => {
                let value = tokens
                    .get(index + 1)
                    .ok_or_else(|| invalid_column(path, line, "missing default value"))?;
                default = Some(value.clone());
                index += 2;
            }
            "on" if tokens.get(index + 1).map(String::as_str) == Some("delete") => {
                let reference_value = reference
                    .as_mut()
                    .ok_or_else(|| invalid_column(path, line, "`on delete` requires `ref`"))?;
                let (action, consumed) =
                    parse_reference_action_tokens(&tokens[index + 2..], path, line)?;
                reference_value.on_delete = Some(action);
                index += 2 + consumed;
            }
            "on" if tokens.get(index + 1).map(String::as_str) == Some("update") => {
                let reference_value = reference
                    .as_mut()
                    .ok_or_else(|| invalid_column(path, line, "`on update` requires `ref`"))?;
                let (action, consumed) =
                    parse_reference_action_tokens(&tokens[index + 2..], path, line)?;
                reference_value.on_update = Some(action);
                index += 2 + consumed;
            }
            other => {
                return Err(invalid_column(
                    path,
                    line,
                    &format!("unsupported column modifier `{other}`"),
                ));
            }
        }
    }

    Ok(Column {
        name,
        ty,
        nullable,
        primary,
        unique,
        index: if no_index {
            false
        } else {
            indexed || reference.is_some()
        },
        default,
        reference,
    })
}

fn infer_implicit_column_type(name: &str) -> Option<ColumnType> {
    match name {
        "id" => Some(ColumnType::Id),
        "remember_token" => Some(ColumnType::RememberToken),
        _ => None,
    }
}

fn parse_type(value: &str, path: &Path, line: &str) -> Result<ColumnType, MigrationError> {
    let value = value.trim();
    Ok(match value {
        "id" => ColumnType::Id,
        "string" => ColumnType::String,
        "text" => ColumnType::Text,
        "int" | "integer" => ColumnType::Integer,
        "bigint" => ColumnType::BigInt,
        "bool" | "boolean" => ColumnType::Boolean,
        "date" => ColumnType::Date,
        "time" => ColumnType::Time,
        "datetime" => ColumnType::DateTime,
        "timestamp" => ColumnType::Timestamp,
        "timestamptz" => ColumnType::TimestampTz,
        "json" | "jsonb" => ColumnType::Json,
        "uuid" | "ulid" => ColumnType::Uuid,
        "remember_token" => ColumnType::RememberToken,
        "float" => ColumnType::Float,
        "double" => ColumnType::Double,
        raw if raw.starts_with("string(") && raw.ends_with(')') => {
            let inner = &raw["string(".len()..raw.len() - 1];
            let length = inner
                .trim()
                .parse::<u32>()
                .map_err(|_| invalid_column(path, line, "invalid string length"))?;
            ColumnType::Varchar(length)
        }
        raw if raw.starts_with("varchar(") && raw.ends_with(')') => {
            let inner = &raw["varchar(".len()..raw.len() - 1];
            let length = inner
                .trim()
                .parse::<u32>()
                .map_err(|_| invalid_column(path, line, "invalid varchar length"))?;
            ColumnType::Varchar(length)
        }
        raw if raw.starts_with("decimal(") && raw.ends_with(')') => {
            let inner = &raw["decimal(".len()..raw.len() - 1];
            let mut parts = inner.split(',').map(str::trim);
            let precision = parts
                .next()
                .ok_or_else(|| invalid_column(path, line, "invalid decimal precision"))?
                .parse::<u32>()
                .map_err(|_| invalid_column(path, line, "invalid decimal precision"))?;
            let scale = parts
                .next()
                .ok_or_else(|| invalid_column(path, line, "invalid decimal scale"))?
                .parse::<u32>()
                .map_err(|_| invalid_column(path, line, "invalid decimal scale"))?;
            ColumnType::Decimal(precision, scale)
        }
        raw if is_identifier(raw) => ColumnType::Custom(raw.to_owned()),
        other => {
            return Err(invalid_column(
                path,
                line,
                &format!("unsupported column type `{other}`"),
            ));
        }
    })
}

fn parse_reference_target(target: &str) -> Reference {
    let (table, column) = target
        .split_once('.')
        .map_or((target, None), |(table, column)| {
            (table, Some(column.to_owned()))
        });
    Reference {
        table: table.to_owned(),
        column,
        on_delete: None,
        on_update: None,
    }
}

fn parse_reference_action_tokens(
    tokens: &[String],
    path: &Path,
    line: &str,
) -> Result<(ReferenceAction, usize), MigrationError> {
    match tokens {
        [value, ..] if value == "cascade" => Ok((ReferenceAction::Cascade, 1)),
        [value, ..] if value == "restrict" => Ok((ReferenceAction::Restrict, 1)),
        [first, second, ..] if first == "set" && second == "null" => {
            Ok((ReferenceAction::SetNull, 2))
        }
        [first, second, ..] if first == "no" && second == "action" => {
            Ok((ReferenceAction::NoAction, 2))
        }
        [value, ..] if value == "set_null" => Ok((ReferenceAction::SetNull, 1)),
        [value, ..] if value == "no_action" => Ok((ReferenceAction::NoAction, 1)),
        [other, ..] => Err(invalid_column(
            path,
            line,
            &format!("unsupported reference action `{other}`"),
        )),
        [] => Err(invalid_column(path, line, "missing reference action")),
    }
}

fn parse_column_list(value: &str, path: &Path) -> Result<Vec<String>, MigrationError> {
    let inner = value
        .trim()
        .strip_prefix('(')
        .and_then(|value| value.strip_suffix(')'))
        .ok_or_else(|| MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("invalid column list `{value}`"),
        })?;

    let columns = inner
        .split(',')
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_owned)
        .collect::<Vec<_>>();

    if columns.is_empty() {
        return Err(MigrationError::InvalidMigrationFile {
            path: path.to_path_buf(),
            message: format!("invalid column list `{value}`"),
        });
    }

    Ok(columns)
}

fn invalid_column(path: &Path, line: &str, message: &str) -> MigrationError {
    MigrationError::InvalidMigrationFile {
        path: path.to_path_buf(),
        message: format!("{message} in `{line}`"),
    }
}

fn tokenize(line: &str) -> Vec<String> {
    let mut tokens = Vec::new();
    let mut current = String::new();
    let mut quote = false;
    let mut paren_depth = 0usize;
    let mut prev = '\0';

    for ch in line.chars() {
        match ch {
            '"' if prev != '\\' => {
                quote = !quote;
                current.push(ch);
            }
            '(' if !quote => {
                paren_depth += 1;
                current.push(ch);
            }
            ')' if !quote => {
                paren_depth = paren_depth.saturating_sub(1);
                current.push(ch);
            }
            c if c.is_whitespace() && !quote && paren_depth == 0 => {
                if !current.is_empty() {
                    tokens.push(std::mem::take(&mut current));
                }
            }
            _ => current.push(ch),
        }
        prev = ch;
    }

    if !current.is_empty() {
        tokens.push(current);
    }

    tokens
}

fn is_identifier(value: &str) -> bool {
    let mut chars = value.chars();
    match chars.next() {
        Some(ch) if ch.is_ascii_alphabetic() || ch == '_' => {}
        _ => return false,
    }
    chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}
