#![no_main]
use core::fmt;
use std::{error::Error, sync::Arc};

use arbitrary::Arbitrary;
use libfuzzer_sys::{fuzz_target, Corpus};

#[derive(Debug, Clone, PartialEq, Eq)]
struct Id(String);

impl<'a> Arbitrary<'a> for Id {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        let len: usize = u.int_in_range(1..=10)?;
        let is_quoted = bool::arbitrary(u)?;

        let mut out = String::with_capacity(len + if is_quoted { 2 } else { 0 });

        if is_quoted {
            out.push('"');
        }

        for _ in 0..len {
            out.push(u.choose(b"abcdefghijklnmopqrstuvwxyz")?.clone() as char);
        }

        if is_quoted {
            out.push('"');
        }

        Ok(Id(out))
    }
}

impl fmt::Display for Id {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Debug, Arbitrary, Clone)]
enum Type {
    None,
    Integer,
    Text,
    Real,
    Blob,
}

impl fmt::Display for Type {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Type::None => Ok(()),
            Type::Integer => write!(f, "INTEGER"),
            Type::Text => write!(f, "TEXT"),
            Type::Real => write!(f, "REAL"),
            Type::Blob => write!(f, "BLOB"),
        }
    }
}

#[derive(Debug, Arbitrary, Clone)]
struct ColumnDef {
    name: Id,
    r#type: Type,
}

impl fmt::Display for ColumnDef {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let ColumnDef { name, r#type } = self;
        write!(f, "{name} {type}",)?;

        Ok(())
    }
}

#[derive(Debug, Clone)]
struct Columns(Vec<ColumnDef>);

impl<'a> Arbitrary<'a> for Columns {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        let len: usize = u.int_in_range(1..=4)?;

        let mut out: Vec<ColumnDef> = Vec::with_capacity(len);

        for i in 0..len {
            out.push(ColumnDef {
                name: Id(format!("c{i}")),
                r#type: u.arbitrary()?,
            });
        }

        Ok(Self(out))
    }
}

impl fmt::Display for Columns {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (i, column) in self.0.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }

            write!(f, "{column}")?;
        }

        Ok(())
    }
}

#[derive(Debug, Clone)]
struct TableDef {
    name: Id,
    columns: Columns,
}

impl fmt::Display for TableDef {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let TableDef { name, columns } = self;

        write!(f, "CREATE TABLE {name} ( {columns} )")
    }
}

#[derive(Debug, Clone)]
struct IndexDef {
    name: Id,
    table: Id,
    columns: Vec<Id>,
}

impl fmt::Display for IndexDef {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let IndexDef {
            name,
            table,
            columns,
        } = self;

        write!(f, "CREATE INDEX {name} ON {table}(")?;

        for (i, column) in columns.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }

            write!(f, "{column}")?;
        }

        write!(f, ")")?;

        Ok(())
    }
}

#[derive(Debug)]
enum Op {
    CreateTable(TableDef),
    CreateIndex(IndexDef),
    DropTable {
        table: Id,
    },
    DropColumn {
        table: Id,
        column: Id,
    },
    RenameTable {
        rename_from: Id,
        rename_to: Id,
    },
    RenameColumn {
        table: Id,
        rename_from: Id,
        rename_to: Id,
    },
}

impl fmt::Display for Op {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Op::CreateTable(table_def) => write!(f, "{table_def}"),
            Op::CreateIndex(index_def) => write!(f, "{index_def}"),
            Op::DropColumn { table, column } => {
                write!(f, "ALTER TABLE {table} DROP COLUMN {column}")
            }
            Op::DropTable { table } => write!(f, "DROP TABLE {table}"),
            Op::RenameTable {
                rename_from,
                rename_to,
            } => write!(f, "ALTER TABLE {rename_from} RENAME TO {rename_to}"),
            Op::RenameColumn {
                table,
                rename_from,
                rename_to,
            } => {
                write!(
                    f,
                    "ALTER TABLE {table} RENAME COLUMN {rename_from} TO {rename_to}"
                )
            }
        }
    }
}

#[derive(Debug)]
struct Ops(Vec<Op>);

impl<'a> Arbitrary<'a> for Ops {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        let mut ops = Vec::new();
        let mut tables = Vec::new();

        let mut drop_list = Vec::new();

        let mut table_index: usize = 0;

        let num_ops = u.int_in_range(1..=10)?;

        for _ in 0..num_ops {
            let op_type = if tables.is_empty() {
                0
            } else {
                u.int_in_range(0..=2)?
            };

            match op_type {
                0 => {
                    let table_def = TableDef {
                        name: {
                            let out = format!("t{table_index}");
                            table_index += 1;

                            Id(out)
                        },
                        columns: u.arbitrary()?,
                    };

                    ops.push(Op::CreateTable(table_def.clone()));

                    tables.push(table_def);
                }
                1 => {
                    let table = u.choose(&tables)?;
                    let index_def = IndexDef {
                        name: {
                            let out = format!("i{table_index}");
                            table_index += 1;

                            Id(out)
                        },
                        table: table.name.clone(),
                        columns: vec![u.choose(&table.columns.0)?.name.clone()],
                    };

                    ops.push(Op::CreateIndex(index_def.clone()));
                }
                2 => {
                    let index = u.choose_index(tables.len())?;

                    let table = &tables[index];

                    let rename_to = Id(format!("t{table_index}"));
                    table_index += 1;

                    ops.push(Op::RenameTable {
                        rename_from: table.name.clone(),
                        rename_to: rename_to.clone(),
                    });

                    tables.push(TableDef {
                        name: rename_to,
                        columns: table.columns.clone(),
                    });

                    tables.remove(index);
                }
                3 => {
                    let index = u.choose_index(tables.len())?;

                    let table = &tables[index];

                    if table.columns.0.len() == 1 {
                        let table = tables.remove(index);

                        ops.push(Op::DropTable {
                            table: table.name.clone(),
                        });

                        drop_list.push(table.name);
                    } else {
                        let table = &mut tables[index];

                        let index = u.choose_index(table.columns.0.len())?;

                        ops.push(Op::DropColumn {
                            table: table.name.clone(),
                            column: table.columns.0.remove(index).name,
                        });
                    }
                }
                4 => {
                    let index = u.choose_index(tables.len())?;

                    let table = &mut tables[index];

                    let index = u.choose_index(table.columns.0.len())?;

                    let rename_to = Id(format!("cr{table_index}"));
                    table_index += 1;

                    let column = table.columns.0[index].clone();

                    table.columns.0.insert(
                        index,
                        ColumnDef {
                            name: rename_to.clone(),
                            ..column
                        },
                    );

                    ops.push(Op::RenameColumn {
                        table: table.name.clone(),
                        rename_from: column.name,
                        rename_to,
                    });
                }
                _ => panic!(),
            }
        }

        Ok(Self(ops))
    }
}

fn do_fuzz(Ops(ops): Ops) -> Result<Corpus, Box<dyn Error>> {
    let rusqlite_conn = rusqlite::Connection::open_in_memory()?;

    let io = Arc::new(turso_core::MemoryIO::new());
    let db = turso_core::Database::open_file(io.clone(), ":memory:", false, true)?;
    let limbo_conn = db.connect()?;

    for op in ops {
        let sql = op.to_string();

        rusqlite_conn
            .execute(&sql, ())
            .inspect_err(|_| {
                dbg!(&sql);
            })
            .unwrap();

        limbo_conn
            .execute(&sql)
            .inspect_err(|_| {
                dbg!(&sql);
            })
            .unwrap()
    }

    Ok(Corpus::Keep)
}

fuzz_target!(|ops: Ops| -> Corpus { do_fuzz(ops).unwrap_or(Corpus::Keep) });
