use crate::config::{BuildConfig, PluginEntry};

pub fn generate_cargo_toml(cfg: &BuildConfig) -> String {
    let mut deps = String::new();

    if let Some(path) = &cfg.build.folk_ext_path {
        deps.push_str(&format!(
            "folk-ext = {{ path = \"{}\", default-features = false }}\n",
            path
        ));
    } else {
        deps.push_str("folk-ext = { version = \"0.3\", default-features = false }\n");
    }

    deps.push_str(
        r#"folk-api = "0.2"
serde_json = "1"
toml = "0.8"
ext-php-rs = "0.15"
bytes = "1"
anyhow = "1"
"#,
    );

    for plugin in &cfg.plugin {
        deps.push_str(&generate_plugin_dep(plugin));
    }

    let patch_section = if let Some(path) = &cfg.build.folk_api_path {
        format!(
            "\n[patch.crates-io]\nfolk-api = {{ path = \"{}\" }}\n",
            path
        )
    } else {
        String::new()
    };

    format!(
        r#"[package]
name = "{output}"
version = "0.1.0"
edition = "2024"

[lib]
name = "{output}"
crate-type = ["cdylib"]

[dependencies]
{deps}
[build-dependencies]
ext-php-rs-build = "0.1"
{patch}"#,
        output = cfg.build.output,
        deps = deps,
        patch = patch_section
    )
}

fn generate_plugin_dep(p: &PluginEntry) -> String {
    let feats = if p.features.is_empty() {
        String::new()
    } else {
        let list = p
            .features
            .iter()
            .map(|f| format!("\"{f}\""))
            .collect::<Vec<_>>()
            .join(", ");
        format!(", features = [{list}]")
    };

    if let Some(path) = &p.path {
        format!("{} = {{ path = \"{}\"{feats} }}\n", p.crate_name, path)
    } else if let Some(git) = &p.git {
        let ver = p.version.as_deref().unwrap_or("0.1");
        format!(
            "{} = {{ git = \"{}\", version = \"{}\"{feats} }}\n",
            p.crate_name, git, ver
        )
    } else {
        let ver = p.version.as_deref().unwrap_or("0.1");
        if feats.is_empty() {
            format!("{} = \"{}\"\n", p.crate_name, ver)
        } else {
            format!("{} = {{ version = \"{}\"{feats} }}\n", p.crate_name, ver)
        }
    }
}

pub fn generate_lib_rs(cfg: &BuildConfig) -> String {
    let imports: Vec<String> = cfg
        .plugin
        .iter()
        .map(|p| {
            let ident = p.crate_name.replace('-', "_");
            format!("use {}::folk_plugin_factory as {}_factory;", ident, ident)
        })
        .collect();

    let registrations: Vec<String> = cfg
        .plugin
        .iter()
        .map(|p| {
            let ident = p.crate_name.replace('-', "_");
            let key = if p.config_key.is_empty() {
                &p.crate_name
            } else {
                &p.config_key
            };
            format!(
                r#"    let cfg_{ident} = raw_cfg.get("{key}").cloned().unwrap_or(toml::Value::Table(Default::default()));
    let cfg_{ident}_json = serde_json::to_value(&cfg_{ident})?;
    plugins.push({ident}_factory().create(cfg_{ident}_json)?);
"#,
            )
        })
        .collect();

    format!(
        r#"//! Generated by folk-builder. Do not edit.
//! Plugins: {plugin_list}

use ext_php_rs::binary::Binary;
use ext_php_rs::prelude::*;
{imports}

fn create_plugins(config_path: &str) -> anyhow::Result<(folk_ext::folk_core::config::FolkConfig, Vec<Box<dyn folk_api::Plugin>>)> {{
    let config = folk_ext::folk_core::config::FolkConfig::load_from(config_path)?;
    let raw_cfg: toml::Table = {{
        let content = std::fs::read_to_string(config_path).unwrap_or_default();
        content.parse().unwrap_or_else(|_| toml::Table::new())
    }};
    let mut plugins: Vec<Box<dyn folk_api::Plugin>> = Vec::new();
{registrations}
    Ok((config, plugins))
}}

// --- PHP wrappers ---

#[php_class]
#[php(name = "Folk\\Server")]
#[derive(Debug)]
pub struct FolkServer {{
    config_path: String,
}}

#[php_impl]
impl FolkServer {{
    pub fn __construct(config_path: String) -> Self {{
        Self {{ config_path }}
    }}

    pub fn start(&self) -> PhpResult<()> {{
        let (config, plugins) = create_plugins(&self.config_path)
            .map_err(|e| PhpException::default(format!("Config error: {{e}}")))?;
        folk_ext::start_server(config, plugins)
            .map_err(|e| PhpException::default(format!("Start error: {{e}}")))?;
        Ok(())
    }}
}}

#[php_function]
pub fn folk_version() -> String {{
    folk_ext::version()
}}

#[php_function]
pub fn folk_call(method: String, payload: Binary<u8>) -> PhpResult<Binary<u8>> {{
    let data: Vec<u8> = payload.into();
    let result = folk_ext::call_method(&method, bytes::Bytes::from(data))
        .map_err(|e| PhpException::default(format!("folk_call({{method}}): {{e}}")))?;
    Ok(Binary::new(result.to_vec()))
}}

#[php_function]
pub fn folk_worker_ready() -> PhpResult<bool> {{
    folk_ext::bridge::do_ready()
        .map_err(|e| PhpException::default(format!("folk_worker_ready: {{e}}")))
}}

#[php_function]
pub fn folk_is_worker_thread() -> bool {{
    folk_ext::bridge::has_worker_state()
}}

#[php_function]
pub fn folk_request_id() -> String {{
    folk_ext::bridge::current_request_id()
        .map(|id| id.to_string())
        .unwrap_or_default()
}}

#[php_function]
#[allow(clippy::needless_pass_by_value)]
pub fn folk_write_head(status: i64, headers_json: String) -> PhpResult<()> {{
    let headers: std::collections::HashMap<String, String> =
        serde_json::from_str(&headers_json).map_err(|e| {{
            PhpException::default(format!("folk_write_head: invalid headers JSON: {{e}}"))
        }})?;
    let status_u16 = u16::try_from(status).map_err(|_| {{
        PhpException::default(format!("folk_write_head: invalid status code {{status}}"))
    }})?;
    folk_ext::bridge::do_write_head(status_u16, headers)
        .map_err(|e| PhpException::default(format!("folk_write_head: {{e}}")))
}}

#[php_function]
#[allow(clippy::needless_pass_by_value)]
pub fn folk_write(data: String) -> PhpResult<()> {{
    folk_ext::bridge::do_write(bytes::Bytes::from(data.into_bytes()))
        .map_err(|e| PhpException::default(format!("folk_write: {{e}}")))
}}

#[php_function]
pub fn folk_write_end() -> PhpResult<()> {{
    folk_ext::bridge::do_write_end()
        .map_err(|e| PhpException::default(format!("folk_write_end: {{e}}")))
}}

#[php_function]
pub fn folk_read(length: i64) -> Binary<u8> {{
    let len = if length <= 0 {{ 8192 }} else {{ usize::try_from(length).unwrap_or(8192) }};
    Binary::new(folk_ext::bridge::do_read(len))
}}

#[php_function]
pub fn folk_read_all() -> Binary<u8> {{
    Binary::new(folk_ext::bridge::do_read_all())
}}

#[php_function]
pub fn folk_next_part() -> Option<String> {{
    folk_ext::bridge::do_next_part()
}}

#[php_function]
pub fn folk_part_read(length: i64) -> Binary<u8> {{
    let len = if length <= 0 {{ 8192 }} else {{ usize::try_from(length).unwrap_or(8192) }};
    Binary::new(folk_ext::bridge::do_part_read(len))
}}

#[php_function]
pub fn folk_part_read_all() -> Binary<u8> {{
    Binary::new(folk_ext::bridge::do_part_read_all())
}}

#[php_function]
pub fn folk_worker_run(dispatch_fn: String) -> PhpResult<()> {{
    folk_ext::bridge::run_dispatch_loop(&dispatch_fn)
        .map_err(|e| PhpException::default(format!("folk_worker_run: {{e}}")))
}}

#[php_module]
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {{
    module
        .class::<FolkServer>()
        .function(wrap_function!(folk_version))
        .function(wrap_function!(folk_call))
        .function(wrap_function!(folk_worker_ready))
        .function(wrap_function!(folk_is_worker_thread))
        .function(wrap_function!(folk_request_id))
        .function(wrap_function!(folk_write_head))
        .function(wrap_function!(folk_write))
        .function(wrap_function!(folk_write_end))
        .function(wrap_function!(folk_read))
        .function(wrap_function!(folk_read_all))
        .function(wrap_function!(folk_next_part))
        .function(wrap_function!(folk_part_read))
        .function(wrap_function!(folk_part_read_all))
        .function(wrap_function!(folk_worker_run))
}}
"#,
        imports = imports.join("\n"),
        registrations = registrations.join(""),
        plugin_list = cfg
            .plugin
            .iter()
            .map(|p| p.crate_name.as_str())
            .collect::<Vec<_>>()
            .join(", ")
    )
}

pub fn generate_build_rs() -> String {
    r#"use ext_php_rs_build::{ApiVersion, PHPInfo, emit_check_cfg, emit_php_cfg_flags, find_php};

fn main() {
    let php = find_php().expect("Failed to find PHP");
    let info = PHPInfo::get(&php).expect("Failed to get PHP info");
    let version: ApiVersion = info
        .zend_version()
        .expect("Failed to get Zend version")
        .try_into()
        .expect("Unsupported PHP version");
    emit_php_cfg_flags(version);
    emit_check_cfg();

    let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
    if target_os == "macos" {
        println!("cargo:rustc-cdylib-link-arg=-undefined");
        println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
    }
}
"#
    .to_string()
}
