// Types for entity generation templates

/// Column information from database schema
pub struct ColumnInfo {
    pub name: String,
    pub col_type: String,
    pub is_nullable: bool,
    pub is_primary_key: bool,
}

/// Table information from database schema
pub struct TableInfo {
    pub name: String,
    pub columns: Vec<ColumnInfo>,
}

// Backend templates

pub fn cargo_toml(package_name: &str, description: &str, author: &str) -> String {
    let authors_line = if author.is_empty() {
        String::new()
    } else {
        format!("authors = [\"{}\"]\n", author)
    };

    format!(
        include_str!("files/backend/Cargo.toml.tpl"),
        package_name = package_name,
        description = description,
        authors_line = authors_line
    )
}

pub fn main_rs() -> &'static str {
    include_str!("files/backend/main.rs.tpl")
}

pub fn routes_rs() -> &'static str {
    include_str!("files/backend/routes.rs.tpl")
}

pub fn controllers_mod() -> &'static str {
    include_str!("files/backend/controllers/mod.rs.tpl")
}

pub fn home_controller() -> &'static str {
    include_str!("files/backend/controllers/home.rs.tpl")
}

// Middleware templates

pub fn middleware_mod() -> &'static str {
    include_str!("files/backend/middleware/mod.rs.tpl")
}

pub fn middleware_logging() -> &'static str {
    include_str!("files/backend/middleware/logging.rs.tpl")
}

/// Template for generating new middleware with make:middleware command
pub fn middleware_template(name: &str, struct_name: &str) -> String {
    format!(
        r#"//! {name} middleware

use kit::{{async_trait, Middleware, Next, Request, Response}};

/// {name} middleware
pub struct {struct_name};

#[async_trait]
impl Middleware for {struct_name} {{
    async fn handle(&self, request: Request, next: Next) -> Response {{
        // TODO: Implement middleware logic
        next(request).await
    }}
}}
"#,
        name = name,
        struct_name = struct_name
    )
}

/// Template for generating new controller with make:controller command
pub fn controller_template(name: &str) -> String {
    format!(
        r#"//! {name} controller

use kit::{{handler, json_response, Request, Response}};

#[handler]
pub async fn invoke(_req: Request) -> Response {{
    json_response!({{
        "controller": "{name}"
    }})
}}
"#,
        name = name
    )
}

/// Template for generating new action with make:action command
pub fn action_template(name: &str, struct_name: &str) -> String {
    format!(
        r#"//! {name} action

use kit::injectable;

#[injectable]
pub struct {struct_name} {{
    // Dependencies injected via container
}}

impl {struct_name} {{
    pub fn execute(&self) {{
        // TODO: Implement action logic
    }}
}}
"#,
        name = name,
        struct_name = struct_name
    )
}

/// Template for generating new Inertia page with make:inertia command
pub fn inertia_page_template(component_name: &str) -> String {
    format!(
        r#"export default function {component_name}() {{
  return (
    <div className="font-sans p-8 max-w-xl mx-auto">
      <h1 className="text-3xl font-bold">{component_name}</h1>
      <p className="mt-2">
        Edit <code className="bg-gray-100 px-1 rounded">frontend/src/pages/{component_name}.tsx</code> to get started.
      </p>
    </div>
  )
}}
"#,
        component_name = component_name
    )
}

/// Template for generating new error with make:error command
pub fn error_template(struct_name: &str) -> String {
    // Convert PascalCase to human readable message
    let mut message = String::new();
    for (i, c) in struct_name.chars().enumerate() {
        if c.is_uppercase() && i > 0 {
            message.push(' ');
            message.push(c.to_lowercase().next().unwrap());
        } else if i == 0 {
            message.push(c);
        } else {
            message.push(c);
        }
    }

    format!(
        r#"//! {struct_name} error

use kit::domain_error;

#[domain_error(status = 500, message = "{message}")]
pub struct {struct_name};
"#,
        struct_name = struct_name,
        message = message
    )
}

/// Template for models/mod.rs
pub fn models_mod() -> &'static str {
    include_str!("files/backend/models/mod.rs.tpl")
}

// Actions templates

pub fn actions_mod() -> &'static str {
    include_str!("files/backend/actions/mod.rs.tpl")
}

pub fn example_action() -> &'static str {
    include_str!("files/backend/actions/example_action.rs.tpl")
}

// Config templates

pub fn config_mod() -> &'static str {
    include_str!("files/backend/config/mod.rs.tpl")
}

pub fn config_database() -> &'static str {
    include_str!("files/backend/config/database.rs.tpl")
}

pub fn config_mail() -> &'static str {
    include_str!("files/backend/config/mail.rs.tpl")
}

pub fn bootstrap() -> &'static str {
    include_str!("files/backend/bootstrap.rs.tpl")
}

// Migrations templates

pub fn migrations_mod() -> &'static str {
    include_str!("files/backend/migrations/mod.rs.tpl")
}

pub fn migrate_bin() -> &'static str {
    include_str!("files/backend/bin/migrate.rs.tpl")
}

// Frontend templates

pub fn package_json(project_name: &str) -> String {
    include_str!("files/frontend/package.json.tpl").replace("{project_name}", project_name)
}

pub fn vite_config() -> &'static str {
    include_str!("files/frontend/vite.config.ts.tpl")
}

pub fn tsconfig() -> &'static str {
    include_str!("files/frontend/tsconfig.json.tpl")
}

pub fn index_html(project_title: &str) -> String {
    include_str!("files/frontend/index.html.tpl").replace("{project_title}", project_title)
}

pub fn main_tsx() -> &'static str {
    include_str!("files/frontend/src/main.tsx.tpl")
}

pub fn home_page() -> &'static str {
    include_str!("files/frontend/src/pages/Home.tsx.tpl")
}

pub fn inertia_props_types() -> &'static str {
    include_str!("files/frontend/src/types/inertia-props.ts.tpl")
}

// Root templates

pub fn gitignore() -> &'static str {
    include_str!("files/root/gitignore.tpl")
}

pub fn env(project_name: &str) -> String {
    include_str!("files/root/env.tpl").replace("{project_name}", project_name)
}

pub fn env_example() -> &'static str {
    include_str!("files/root/env.example.tpl")
}

// Entity generation templates for db:sync command

/// Generate auto-generated entity file (regenerated on every sync)
pub fn entity_template(table_name: &str, columns: &[ColumnInfo]) -> String {
    let _struct_name = to_pascal_case(&singularize(table_name));

    // Generate column fields
    let column_fields: Vec<String> = columns
        .iter()
        .map(|col| {
            let rust_type = sql_type_to_rust_type(col);
            let mut attrs = Vec::new();

            if col.is_primary_key {
                attrs.push("    #[sea_orm(primary_key)]".to_string());
            }

            let field = format!("    pub {}: {},", col.name, rust_type);
            if attrs.is_empty() {
                field
            } else {
                format!("{}\n{}", attrs.join("\n"), field)
            }
        })
        .collect();

    // Find primary key columns (reserved for future use)
    let _pk_columns: Vec<&ColumnInfo> = columns.iter().filter(|c| c.is_primary_key).collect();

    format!(
        r#"// AUTO-GENERATED FILE - DO NOT EDIT
// Generated by `kit db:sync` - Changes will be overwritten
// Add custom code to src/models/{table_name}.rs instead

use sea_orm::entity::prelude::*;
use serde::Serialize;

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize)]
#[sea_orm(table_name = "{table_name}")]
pub struct Model {{
{columns}
}}

// Note: Relation enum is required here for DeriveEntityModel macro.
// Define your actual relations in src/models/{table_name}.rs using the Related trait.
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {{}}
"#,
        table_name = table_name,
        columns = column_fields.join("\n"),
    )
}

/// Generate user model file with Eloquent-like API (created only once, never overwritten)
pub fn user_model_template(table_name: &str, struct_name: &str, columns: &[ColumnInfo]) -> String {
    let model_setters = generate_model_setters(columns);
    let builder_fields = generate_builder_fields(columns);
    let builder_setters = generate_builder_setters(columns);
    let builder_to_active = generate_builder_to_active(columns);
    let model_to_active = generate_model_to_active(columns);
    let pk_field = columns
        .iter()
        .find(|c| c.is_primary_key)
        .map(|c| c.name.as_str())
        .unwrap_or("id");

    format!(
        r#"//! {struct_name} model
//!
//! This file contains custom implementations for the {struct_name} model.
//! The base entity is auto-generated in src/models/entities/{table_name}.rs
//!
//! This file is NEVER overwritten by `kit db:sync` - your custom code is safe here.

// Re-export the auto-generated entity
pub use super::entities::{table_name}::*;

use kit::database::{{ModelMut, QueryBuilder}};
use sea_orm::{{entity::prelude::*, Set}};

/// Type alias for convenient access
pub type {struct_name} = Model;

// ============================================================================
// ENTITY CONFIGURATION
// ============================================================================

impl ActiveModelBehavior for ActiveModel {{}}

impl kit::database::Model for Entity {{}}
impl kit::database::ModelMut for Entity {{}}

// ============================================================================
// ELOQUENT-LIKE API
// Fluent query builder and setter methods for {struct_name}
// ============================================================================

impl Model {{
    /// Start a new query builder
    ///
    /// # Example
    /// ```rust,ignore
    /// let records = {struct_name}::query().all().await?;
    /// let record = {struct_name}::query().filter(Column::Id.eq(1)).first().await?;
    /// ```
    pub fn query() -> QueryBuilder<Entity> {{
        QueryBuilder::new()
    }}

    /// Create a new record builder
    ///
    /// # Example
    /// ```rust,ignore
    /// let record = {struct_name}::create()
    ///     .set_field("value")
    ///     .insert()
    ///     .await?;
    /// ```
    pub fn create() -> {struct_name}Builder {{
        {struct_name}Builder::default()
    }}

{model_setters}
    /// Save changes to the database
    ///
    /// # Example
    /// ```rust,ignore
    /// let updated = record.set_field("new_value").update().await?;
    /// ```
    pub async fn update(self) -> Result<Self, kit::FrameworkError> {{
        let active = self.to_active_model();
        Entity::update_one(active).await
    }}

    /// Delete this record from the database
    ///
    /// # Example
    /// ```rust,ignore
    /// record.delete().await?;
    /// ```
    pub async fn delete(self) -> Result<u64, kit::FrameworkError> {{
        Entity::delete_by_pk(self.{pk_field}).await
    }}

    fn to_active_model(&self) -> ActiveModel {{
{model_to_active}
    }}
}}

// ============================================================================
// BUILDER
// For creating new records with fluent setter pattern
// ============================================================================

/// Builder for creating new {struct_name} records
#[derive(Default)]
pub struct {struct_name}Builder {{
{builder_fields}
}}

impl {struct_name}Builder {{
{builder_setters}
    /// Insert the record into the database
    ///
    /// # Example
    /// ```rust,ignore
    /// let record = {struct_name}::create()
    ///     .set_field("value")
    ///     .insert()
    ///     .await?;
    /// ```
    pub async fn insert(self) -> Result<Model, kit::FrameworkError> {{
        let active = self.build();
        Entity::insert_one(active).await
    }}

    fn build(self) -> ActiveModel {{
{builder_to_active}
    }}
}}

// ============================================================================
// CUSTOM METHODS
// Add your custom query and mutation methods below
// ============================================================================

// Example custom finder:
// impl Model {{
//     pub async fn find_by_email(email: &str) -> Result<Option<Self>, kit::FrameworkError> {{
//         Self::query().filter(Column::Email.eq(email)).first().await
//     }}
// }}

// ============================================================================
// RELATIONS
// Define relationships to other entities here
// ============================================================================

// Example: One-to-Many relation
// impl Entity {{
//     pub fn has_many_posts() -> RelationDef {{
//         Entity::has_many(super::posts::Entity).into()
//     }}
// }}

// Example: Belongs-To relation
// impl Entity {{
//     pub fn belongs_to_user() -> RelationDef {{
//         Entity::belongs_to(super::users::Entity)
//             .from(Column::UserId)
//             .to(super::users::Column::Id)
//             .into()
//     }}
// }}
"#,
        struct_name = struct_name,
        table_name = table_name,
        model_setters = model_setters,
        builder_fields = builder_fields,
        builder_setters = builder_setters,
        builder_to_active = builder_to_active,
        model_to_active = model_to_active,
        pk_field = pk_field,
    )
}

/// Generate entities/mod.rs (regenerated on every sync)
pub fn entities_mod_template(tables: &[TableInfo]) -> String {
    let mut content =
        String::from("// AUTO-GENERATED FILE - DO NOT EDIT\n// Generated by `kit db:sync`\n\n");

    for table in tables {
        content.push_str(&format!("pub mod {};\n", table.name));
    }

    content
}

// Helper functions for entity generation

fn sql_type_to_rust_type(col: &ColumnInfo) -> String {
    let col_type_upper = col.col_type.to_uppercase();
    let base_type = if col_type_upper.contains("INT") {
        if col_type_upper.contains("BIGINT") || col_type_upper.contains("INT8") {
            "i64"
        } else if col_type_upper.contains("SMALLINT") || col_type_upper.contains("INT2") {
            "i16"
        } else {
            "i32"
        }
    } else if col_type_upper.contains("TEXT")
        || col_type_upper.contains("VARCHAR")
        || col_type_upper.contains("CHAR")
        || col_type_upper.contains("CHARACTER")
    {
        "String"
    } else if col_type_upper.contains("BOOL") {
        "bool"
    } else if col_type_upper.contains("REAL") || col_type_upper.contains("FLOAT4") {
        "f32"
    } else if col_type_upper.contains("DOUBLE") || col_type_upper.contains("FLOAT8") {
        "f64"
    } else if col_type_upper.contains("TIMESTAMP") || col_type_upper.contains("DATETIME") {
        "DateTimeUtc"
    } else if col_type_upper.contains("DATE") {
        "Date"
    } else if col_type_upper.contains("TIME") {
        "Time"
    } else if col_type_upper.contains("UUID") {
        "Uuid"
    } else if col_type_upper.contains("JSON") {
        "Json"
    } else if col_type_upper.contains("BYTEA") || col_type_upper.contains("BLOB") {
        "Vec<u8>"
    } else if col_type_upper.contains("DECIMAL") || col_type_upper.contains("NUMERIC") {
        "Decimal"
    } else {
        "String" // fallback
    };

    if col.is_nullable {
        format!("Option<{}>", base_type)
    } else {
        base_type.to_string()
    }
}

fn to_pascal_case(s: &str) -> String {
    let mut result = String::new();
    let mut capitalize_next = true;

    for c in s.chars() {
        if c == '_' || c == '-' || c == ' ' {
            capitalize_next = true;
        } else if capitalize_next {
            result.push(c.to_uppercase().next().unwrap());
            capitalize_next = false;
        } else {
            result.push(c);
        }
    }
    result
}

fn singularize(word: &str) -> String {
    if word.ends_with("ies") {
        format!("{}y", &word[..word.len() - 3])
    } else if word.ends_with("es") && !word.ends_with("ses") && !word.ends_with("xes") {
        word[..word.len() - 2].to_string()
    } else if word.ends_with("s") && !word.ends_with("ss") && !word.ends_with("us") {
        word[..word.len() - 1].to_string()
    } else {
        word.to_string()
    }
}

// ============================================================================
// Eloquent-like API Code Generation Helpers
// ============================================================================

/// Generate setter methods for the Model (used for updates)
fn generate_model_setters(columns: &[ColumnInfo]) -> String {
    columns
        .iter()
        .filter(|c| !c.is_primary_key && !is_timestamp_field(&c.name))
        .map(|col| {
            let rust_type = sql_type_to_rust_type(col);
            let setter_input_type = get_setter_input_type(&rust_type, col.is_nullable);

            format!(
                r#"    /// Set the {} field
    pub fn set_{field}(mut self, value: {input_type}) -> Self {{
        self.{field} = {assignment};
        self
    }}

"#,
                col.name,
                field = col.name,
                input_type = setter_input_type,
                assignment = get_setter_assignment(&rust_type, col.is_nullable),
            )
        })
        .collect::<Vec<_>>()
        .join("")
}

/// Generate builder struct fields
fn generate_builder_fields(columns: &[ColumnInfo]) -> String {
    columns
        .iter()
        .filter(|c| !c.is_primary_key)
        .map(|col| {
            let rust_type = sql_type_to_rust_type(col);
            format!("    {}: Option<{}>,", col.name, rust_type)
        })
        .collect::<Vec<_>>()
        .join("\n")
}

/// Generate setter methods for the Builder (used for creates)
fn generate_builder_setters(columns: &[ColumnInfo]) -> String {
    columns
        .iter()
        .filter(|c| !c.is_primary_key && !is_timestamp_field(&c.name))
        .map(|col| {
            let rust_type = sql_type_to_rust_type(col);
            let setter_input_type = get_builder_setter_input_type(&rust_type, col.is_nullable);

            format!(
                r#"    /// Set the {} field
    pub fn set_{field}(mut self, value: {input_type}) -> Self {{
        self.{field} = Some({builder_assignment});
        self
    }}

"#,
                col.name,
                field = col.name,
                input_type = setter_input_type,
                builder_assignment = get_builder_setter_assignment(&rust_type, col.is_nullable),
            )
        })
        .collect::<Vec<_>>()
        .join("")
}

/// Generate code to convert Builder to ActiveModel
fn generate_builder_to_active(columns: &[ColumnInfo]) -> String {
    let mut lines = vec!["        ActiveModel {".to_string()];

    for col in columns {
        if col.is_primary_key {
            lines.push(format!(
                "            {}: sea_orm::ActiveValue::NotSet,",
                col.name
            ));
        } else {
            lines.push(format!(
                "            {field}: self.{field}.map(Set).unwrap_or(sea_orm::ActiveValue::NotSet),",
                field = col.name
            ));
        }
    }

    lines.push("        }".to_string());
    lines.join("\n")
}

/// Generate code to convert Model to ActiveModel for updates
fn generate_model_to_active(columns: &[ColumnInfo]) -> String {
    let mut lines = vec!["        ActiveModel {".to_string()];

    for col in columns {
        let rust_type = sql_type_to_rust_type(col);
        let needs_clone = needs_clone_for_type(&rust_type);

        if needs_clone {
            lines.push(format!(
                "            {field}: Set(self.{field}.clone()),",
                field = col.name
            ));
        } else {
            lines.push(format!(
                "            {field}: Set(self.{field}),",
                field = col.name
            ));
        }
    }

    lines.push("        }".to_string());
    lines.join("\n")
}

/// Check if field is a timestamp field (auto-managed)
fn is_timestamp_field(name: &str) -> bool {
    matches!(name, "created_at" | "updated_at" | "deleted_at")
}

/// Get the input type for a setter method on Model
fn get_setter_input_type(rust_type: &str, is_nullable: bool) -> String {
    if is_nullable {
        // For Option<String>, accept Option<impl Into<String>>
        if rust_type == "Option<String>" {
            "Option<impl Into<String>>".to_string()
        } else {
            rust_type.to_string()
        }
    } else if rust_type == "String" {
        "impl Into<String>".to_string()
    } else {
        rust_type.to_string()
    }
}

/// Get the assignment expression for a setter on Model
fn get_setter_assignment(rust_type: &str, is_nullable: bool) -> String {
    if is_nullable {
        if rust_type == "Option<String>" {
            "value.map(|v| v.into())".to_string()
        } else {
            "value".to_string()
        }
    } else if rust_type == "String" {
        "value.into()".to_string()
    } else {
        "value".to_string()
    }
}

/// Get the input type for a builder setter method
fn get_builder_setter_input_type(rust_type: &str, is_nullable: bool) -> String {
    if is_nullable {
        // For nullable fields in builder, accept the inner type (not Option)
        if rust_type == "Option<String>" {
            "impl Into<String>".to_string()
        } else if rust_type.starts_with("Option<") && rust_type.ends_with(">") {
            // Extract inner type from Option<T>
            rust_type[7..rust_type.len() - 1].to_string()
        } else {
            rust_type.to_string()
        }
    } else if rust_type == "String" {
        "impl Into<String>".to_string()
    } else {
        rust_type.to_string()
    }
}

/// Get the assignment expression for a builder setter
fn get_builder_setter_assignment(rust_type: &str, is_nullable: bool) -> String {
    if is_nullable {
        // Wrap in Some for nullable fields
        if rust_type == "Option<String>" {
            "Some(value.into())".to_string()
        } else {
            "Some(value)".to_string()
        }
    } else if rust_type == "String" {
        "value.into()".to_string()
    } else {
        "value".to_string()
    }
}

/// Check if a type needs .clone() when converting
fn needs_clone_for_type(rust_type: &str) -> bool {
    // Types that implement Copy don't need clone
    let copy_types = [
        "i8", "i16", "i32", "i64", "i128", "u8", "u16", "u32", "u64", "u128", "f32", "f64", "bool",
    ];

    // Check if it's a Copy type
    if copy_types.contains(&rust_type) {
        return false;
    }

    // Option<Copy> types also don't need clone
    for copy_type in &copy_types {
        if rust_type == format!("Option<{}>", copy_type) {
            return false;
        }
    }

    // Everything else needs clone (String, Option<String>, DateTimeUtc, etc.)
    true
}
