// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

use crate::add_template;
use crate::content::add_obj;
use anyhow::bail;
use fs_err::File;
use ordinary_config::TemplateRefFieldBind::Segment;
use ordinary_config::{
    AssetsConfig, Content, ContentDefinition, ContentObjectLifecycle, FragmentsConfig, Global,
    LifecycleBeforeAfterScripts, OrdinaryConfig, TemplateConfig, TemplateFfi,
    TemplateFfiSerialization, TemplateFfiVersion, TemplateRef, TemplateRefField, TopLevelLifecycle,
};
use ordinary_types::{ContentField, ContentObject, Field, Kind, TimeUnit};
use serde_json::Value;
use std::io::Write;
use std::path::Path;
use std::time::SystemTime;
use tracing::instrument;

const GITIGNORE: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/.gitignore"
));

const README: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/README.md"
));

const HOOKS_POSTS_SRC_MAIN_RS: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/hooks/posts/src/main.rs"
));

const HOOKS_POSTS_CARGO_TOML: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/hooks/posts/Freight.toml"
));

const TMP_CSS: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/assets/themes/tmp.css"
));

const FAVICON: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/assets/favicon.svg"
));

const INDEX: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/templates/index.html"
));

const POST: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/templates/post.html"
));

const SITEMAP: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/templates/sitemap.xml"
));

const ROBOTS: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/templates/robots.txt"
));

const RSS: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/templates/rss.xml"
));

const HEADER: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/fragments/header.html"
));

const FOOTER: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/project/fragments/footer.html"
));

#[allow(clippy::too_many_lines)]
#[instrument(skip_all, err)]
pub fn new(path: &str, domain: &str) -> anyhow::Result<()> {
    let path = Path::new(path).join(domain);

    if path.exists() {
        bail!("project already exists");
    }

    tracing::info!("creating project at {:?}", path);
    fs_err::create_dir_all(&path)?;

    let hooks_posts_path = path.join("hooks").join("posts");
    fs_err::create_dir_all(hooks_posts_path.join("src"))?;

    let mut file = File::create(hooks_posts_path.join("src").join("main.rs"))?;
    file.write_all(HOOKS_POSTS_SRC_MAIN_RS)?;
    file.flush()?;

    let mut file = File::create(hooks_posts_path.join("Cargo.toml"))?;
    file.write_all(HOOKS_POSTS_CARGO_TOML)?;
    file.flush()?;

    let app_config = OrdinaryConfig {
        lifecycle: Some(TopLevelLifecycle {
            before_all: Some(vec![
                [
                    "cargo",
                    "build",
                    "--release",
                    "--manifest-path",
                    "hooks/posts/Cargo.toml",
                ]
                .iter()
                .map(ToString::to_string)
                .collect::<Vec<_>>(),
            ]),
            build: Some(LifecycleBeforeAfterScripts {
                before: Some(vec![
                    [
                        "hooks/posts/target/release/posts",
                        "before_build",
                        "content/posts",
                    ]
                    .iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>(),
                ]),
                after: None,
            }),
        }),
        domain: domain.to_string(),

        version: "0.1.0".into(),
        contacts: Some(vec![]),

        storage_size: Some(5_000_000),

        port: Some(4433),
        redirect_port: Some(8080),
        globals: Some(vec![Global {
            name: "author".to_string(),
            kind: Kind::String,
            value: "Your Name".into(),
        }]),

        fragments: Some(FragmentsConfig {
            dir_path: "fragments".to_string(),
        }),
        error: None,
        assets: Some(AssetsConfig {
            dir_path: Some("assets".to_string()),
            base_route: "/assets".to_string(),
            ..Default::default()
        }),

        content: Some(Content {
            file_path: "./content.json".to_string(),
            definitions: vec![ContentDefinition {
                idx: 0,
                name: "post".to_string(),
                fields: vec![
                    Field {
                        idx: 0,
                        name: "slug".to_string(),
                        kind: Kind::String,
                        indexed: Some(true),
                        ..Default::default()
                    },
                    Field {
                        idx: 1,
                        name: "title".to_string(),
                        kind: Kind::String,
                        ..Default::default()
                    },
                    Field {
                        idx: 2,
                        name: "date".to_string(),
                        kind: Kind::Timestamp {
                            unit: TimeUnit::Seconds,
                        },
                        ..Default::default()
                    },
                    Field {
                        idx: 3,
                        name: "body".to_string(),
                        kind: Kind::Markdown,
                        ..Default::default()
                    },
                ],
                lifecycle: Some(ContentObjectLifecycle {
                    before_all: None,
                    on_add: Some(LifecycleBeforeAfterScripts {
                        after: Some(vec![
                            [
                                "hooks/posts/target/release/posts",
                                "after_add",
                                "content/posts",
                            ]
                            .iter()
                            .map(ToString::to_string)
                            .collect::<Vec<_>>(),
                        ]),
                        before: None,
                    }),
                    on_edit: Some(LifecycleBeforeAfterScripts {
                        after: Some(vec![
                            [
                                "hooks/posts/target/release/posts",
                                "after_edit",
                                "content/posts",
                            ]
                            .iter()
                            .map(ToString::to_string)
                            .collect::<Vec<_>>(),
                        ]),
                        before: None,
                    }),
                    on_delete: Some(LifecycleBeforeAfterScripts {
                        after: Some(vec![
                            [
                                "hooks/posts/target/release/posts",
                                "after_delete",
                                "content/posts",
                            ]
                            .iter()
                            .map(ToString::to_string)
                            .collect::<Vec<_>>(),
                        ]),
                        before: None,
                    }),
                }),
            }],
            ..Default::default()
        }),

        templates: Some(vec![
            TemplateConfig {
                ffi: TemplateFfi {
                    version: TemplateFfiVersion::V1,
                    serialization: TemplateFfiSerialization::FlexBufferVector,
                },
                idx: 0,
                name: "index".to_string(),
                mime: "text/html".to_string(),
                path: Some("./templates/index.html".to_string()),
                route: "/".to_string(),
                globals: Some(vec!["author".to_string()]),
                content: Some(vec![TemplateRef {
                    idx: 0,
                    name: "post".to_string(),
                    fields: vec![
                        TemplateRefField {
                            idx: 0,
                            name: "slug".to_string(),
                            ..Default::default()
                        },
                        TemplateRefField {
                            idx: 1,
                            name: "title".to_string(),
                            ..Default::default()
                        },
                        TemplateRefField {
                            idx: 2,
                            name: "date".to_string(),
                            ..Default::default()
                        },
                    ],
                    all: Some("posts".to_string()),
                }]),
                ..Default::default()
            },
            TemplateConfig {
                ffi: TemplateFfi {
                    version: TemplateFfiVersion::V1,
                    serialization: TemplateFfiSerialization::FlexBufferVector,
                },
                idx: 1,
                name: "post".to_string(),
                mime: "text/html".to_string(),
                path: Some("./templates/post.html".to_string()),
                route: "/posts/{slug}".to_string(),
                globals: Some(vec!["author".to_string()]),
                content: Some(vec![TemplateRef {
                    idx: 0,
                    name: "post".to_string(),
                    fields: vec![
                        TemplateRefField {
                            idx: 0,
                            name: "slug".to_string(),
                            bind: Some(Segment {
                                name: "slug".to_string(),
                                expression: None,
                            }),
                            ..Default::default()
                        },
                        TemplateRefField {
                            idx: 1,
                            name: "title".to_string(),
                            ..Default::default()
                        },
                        TemplateRefField {
                            idx: 2,
                            name: "date".to_string(),
                            ..Default::default()
                        },
                        TemplateRefField {
                            idx: 3,
                            name: "body".to_string(),
                            ..Default::default()
                        },
                    ],
                    all: None,
                }]),
                ..Default::default()
            },
        ]),
        ..Default::default()
    };

    let ordinary_json = serde_json::to_string_pretty(&app_config)?;

    let mut file = File::create(path.join("ordinary.json"))?;
    file.write_all(ordinary_json.as_bytes())?;
    file.flush()?;

    let mut file = File::create(path.join("content.json"))?;
    file.write_all(b"[]")?;
    file.flush()?;

    let first_post_json = serde_json::to_string_pretty(&ContentObject {
        uuid: uuid::Uuid::new_v4().to_string(),
        instance_of: "post".to_string(),
        fields: vec![
            ContentField {
                name: "slug".to_string(),
                value: Value::String("hello-world".to_string()),
            },
            ContentField {
                name: "title".to_string(),
                value: Value::String("Hello World!".to_string()),
            },
            ContentField {
                name: "date".to_string(),
                value: Value::Number(
                    SystemTime::now()
                        .duration_since(SystemTime::UNIX_EPOCH)?
                        .as_secs()
                        .into(),
                ),
            },
            ContentField {
                name: "body".to_string(),
                value: Value::String(
                    "This is your first blog post generated with [Ordinary](https://codeberg.org/ordinarylabs/Ordinary).".to_string(),
                ),
            },
        ],
    })?;

    if let Some(path_str) = path.to_str() {
        add_obj(path_str, &first_post_json)?;
    }

    let mut file = File::create(path.join(".gitignore"))?;
    file.write_all(GITIGNORE)?;
    file.flush()?;

    let mut file = File::create(path.join("README.md"))?;
    file.write_all(README)?;
    file.flush()?;

    let assets_dir = path.join("assets");
    let themes_dir = assets_dir.join("themes");

    fs_err::create_dir_all(&themes_dir)?;

    let mut file = File::create(themes_dir.join("tmp.css"))?;
    file.write_all(TMP_CSS)?;
    file.flush()?;

    let mut file = File::create(assets_dir.join("favicon.svg"))?;
    file.write_all(FAVICON)?;
    file.flush()?;

    let fragments_dir = path.join("fragments");
    fs_err::create_dir_all(&fragments_dir)?;

    let mut header_file = File::create(fragments_dir.join("header.html"))?;
    header_file.write_all(HEADER)?;
    header_file.flush()?;

    let mut footer_file = File::create(fragments_dir.join("footer.html"))?;
    footer_file.write_all(FOOTER)?;
    footer_file.flush()?;

    let html_globals = Some(vec!["author".to_string()]);

    if let Some(path_str) = path.to_str() {
        let templates_dir = path.join("templates");
        fs_err::create_dir_all(&templates_dir)?;

        let mut index_file = File::create(templates_dir.join("index.html"))?;
        index_file.write_all(INDEX)?;
        index_file.flush()?;

        let mut post_file = File::create(templates_dir.join("post.html"))?;
        post_file.write_all(POST)?;
        post_file.flush()?;

        add_template(
            path_str,
            "error",
            "/error",
            "text/html",
            r#"
    <link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
    <link rel="stylesheet" href="/assets/themes/tmp.css">
"#,
            r#"{% include "fragments/header.html" %}"#,
            r#"{% include "fragments/footer.html" %}"#,
            true,
            html_globals,
            None,
        )?;

        add_template(
            path_str,
            "sitemap",
            "/sitemap.xml",
            "text/xml",
            "",
            "",
            "",
            false,
            None,
            Some(vec![TemplateRef {
                idx: 0,
                name: "post".to_string(),
                fields: vec![
                    TemplateRefField {
                        idx: 0,
                        name: "slug".to_string(),
                        ..Default::default()
                    },
                    TemplateRefField {
                        idx: 1,
                        name: "date".to_string(),
                        ..Default::default()
                    },
                ],
                all: Some("posts".to_string()),
            }]),
        )?;

        let mut sitemap_file = File::create(templates_dir.join("sitemap.xml"))?;
        sitemap_file.write_all(SITEMAP)?;
        sitemap_file.flush()?;

        add_template(
            path_str,
            "robots",
            "/robots.txt",
            "text/plain",
            "",
            "",
            "",
            false,
            None,
            None,
        )?;

        let mut robots_file = File::create(templates_dir.join("robots.txt"))?;
        robots_file.write_all(ROBOTS)?;
        robots_file.flush()?;

        add_template(
            path_str,
            "rss",
            "/rss",
            "application/rss+xml",
            "",
            "",
            "",
            false,
            Some(vec!["author".to_string()]),
            Some(vec![TemplateRef {
                idx: 0,
                name: "post".to_string(),
                fields: vec![
                    TemplateRefField {
                        idx: 0,
                        name: "slug".to_string(),
                        ..Default::default()
                    },
                    TemplateRefField {
                        idx: 1,
                        name: "title".to_string(),
                        ..Default::default()
                    },
                    TemplateRefField {
                        idx: 2,
                        name: "date".to_string(),
                        ..Default::default()
                    },
                ],
                all: Some("posts".to_string()),
            }]),
        )?;

        let mut robots_file = File::create(templates_dir.join("rss.xml"))?;
        robots_file.write_all(RSS)?;
        robots_file.flush()?;
    }

    Ok(())
}
