use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse::Parse, parse::ParseStream, LitStr, Token};

/// Input for the migs! macro
/// Can be either:
/// - migs!(path = "path/to/file.sql")  // scope defaults to "init"
/// - migs!(path = "path/to/file.sql", scope = "...")
/// - migs!(sql = "SELECT * FROM {{table}}")  // scope defaults to "init"
/// - migs!(sql = "SELECT * FROM {{table}}", scope = "...")
struct MigsInput {
    kind: MigsKind,
    scope: String,
}

enum MigsKind {
    Path(String),
    Sql(String),
}

impl Parse for MigsInput {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();

        let kind = if lookahead.peek(syn::Ident) {
            let ident: syn::Ident = input.parse()?;
            let ident_str = ident.to_string();

            if ident_str == "path" {
                input.parse::<Token![=]>()?;
                let path_lit: LitStr = input.parse()?;
                MigsKind::Path(path_lit.value())
            } else if ident_str == "sql" {
                input.parse::<Token![=]>()?;
                let sql_lit: LitStr = input.parse()?;
                MigsKind::Sql(sql_lit.value())
            } else {
                return Err(syn::Error::new(ident.span(), "expected 'path' or 'sql'"));
            }
        } else {
            return Err(lookahead.error());
        };

        // Parse optional comma and scope, defaulting to "init"
        let mut scope = "init".to_string();
        if input.peek(Token![,]) {
            input.parse::<Token![,]>()?;
            let scope_ident: syn::Ident = input.parse()?;
            if scope_ident != "scope" {
                return Err(syn::Error::new(scope_ident.span(), "expected 'scope'"));
            }
            input.parse::<Token![=]>()?;
            let scope_lit: LitStr = input.parse()?;
            scope = scope_lit.value();
        }

        Ok(MigsInput { kind, scope })
    }
}

/// The migs! macro registers a SQL script at compile time
/// Usage:
/// - migs!(path = "migrations/001_init.sql")              // scope defaults to "init"
/// - migs!(path = "migrations/001_init.sql", scope = "init")
/// - migs!(sql = "CREATE TABLE {{name}} (id INT)")      // scope defaults to "init"
/// - migs!(sql = "CREATE TABLE {{name}} (id INT)", scope = "create")
///
/// This macro can be used at module level to register migrations.
#[proc_macro]
pub fn migs(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as MigsInput);

    let (content, source) = match &input.kind {
        MigsKind::Path(path) => {
            // Read file at compile time
            let content = match std::fs::read_to_string(path) {
                Ok(c) => c,
                Err(e) => {
                    return syn::Error::new(
                        Span::call_site(),
                        format!("Failed to read file '{}': {}", path, e),
                    )
                    .to_compile_error()
                    .into();
                }
            };
            (content, path.clone())
        }
        MigsKind::Sql(sql) => (sql.clone(), "<inline>".to_string()),
    };

    let scope = input.scope;

    // Generate code that registers this migration using inventory
    // We construct the Migs directly - strings become &'static str baked into binary
    let expanded = quote! {
        ::migs::inventory::submit! {
            ::migs::Migs {
                scope: #scope,
                source: #source,
                content: #content,
            }
        }
    };

    TokenStream::from(expanded)
}

/// The collect! macro returns a Vec<&'static Migs> with all registered scripts
#[proc_macro]
pub fn collect(_input: TokenStream) -> TokenStream {
    // Use the inventory crate to collect all registered Migs
    let expanded = quote! {
        {
            use ::migs::inventory;
            inventory::iter::<::migs::Migs>
                .into_iter()
                .collect::<Vec<_>>()
        }
    };

    TokenStream::from(expanded)
}
