use crate::nostru;
use crate::rendering::geminize_note;
use crate::routes::route_prelude::*;
use crate::urls::titan_url;
use rand::distributions::{Alphanumeric, DistString};

#[derive(Template)]
#[template(path = "note_draft_edit.gmi", escape = "txt")]
pub struct NoteDraftEditTemplate<'c> {
    url: Url,
    draft_id: String,
    draft_blocks: Vec<Block>,
    draft_attachments: Vec<&'c str>,
}

#[derive(Template)]
#[template(path = "notes/text/draft_mention_npub_select.gmi", escape = "txt")]
pub struct NoteDraftMentionNpubTemplate {
    metadata_events: Events,
}

pub async fn draft_new(
    ctx: RouteContext,
    _user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let id = Alphanumeric
        .sample_string(&mut rand::thread_rng(), 16)
        .to_string();

    Ok(Response::temporary_redirect(titan_url(
        ctx.url,
        &format!("/draft/{}/edit", id),
    )))
}

pub async fn draft_edit(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let page_url = ctx.url.clone();

    let draft_id = ctx.draft_id().ok_or(UserError::InvalidParams)?;

    if let Some(titan) = ctx.titan_rsc {
        let Ok(rsc_text) = titan.content_text() else {
            return Ok(resp_error_generic());
        };

        // Filter out gemini/titan links and empty lines
        let rsc_text = rsc_text
            .lines()
            .filter(|l| !l.starts_with("=> titan://"))
            .filter(|l| !l.starts_with("=> gemini://"))
            .filter(|l| !l.is_empty())
            .collect::<Vec<&str>>()
            .join("\n");

        let _ = user.storage.draft_store(&draft_id, rsc_text.as_str());
    }

    let Ok(mut rtxn) = user.storage.get_read_txn() else {
        return Ok(resp_invalid_params());
    };

    let Ok(draft) = user.storage.draft_get(&draft_id, &mut rtxn) else {
        return Ok(resp_error_generic());
    };

    let draft_blocks = geminize_note(draft.content, &user.client).await;

    let template = NoteDraftEditTemplate {
        url: page_url,
        draft_id: draft_id.to_string(),
        draft_blocks,
        draft_attachments: draft.attachments,
    };

    Ok(Response::success(WindTemplate::render(template)))
}

pub async fn draft_body_append(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let Some(text) = ctx.url.query() else {
        return Ok(Response::input("Text ?"));
    };

    let Ok(text) = decode(text) else {
        return Ok(resp_invalid_params());
    };

    let draft_id = ctx.draft_id().ok_or(UserError::InvalidParams)?;

    let Ok(mut rtxn) = user.storage.get_read_txn() else {
        return Err(UserError::StorageError);
    };

    let Ok(mut content) = user.storage.draft_get_content(&draft_id, &mut rtxn)
    else {
        return Err(UserError::StorageError);
    };

    content.push_str(&format!("\n{text}\n"));

    match user.storage.draft_store(&draft_id, &content) {
        Ok(_) => Ok(Response::temporary_redirect(format!(
            "gemini://{}/draft/{}/edit",
            ctx.url.authority(),
            draft_id
        ))),
        Err(_) => Ok(Response::temporary_failure("Draft store failure")),
    }
}

pub async fn draft_post(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let draft_id = ctx.draft_id().ok_or(UserError::InvalidParams)?;

    let Ok(mut rtxn) = user.storage.get_read_txn() else {
        return Ok(resp_invalid_params());
    };

    let Ok(draft) = user.storage.draft_get(&draft_id, &mut rtxn) else {
        return Ok(resp_error_generic());
    };

    let body = draft.render_text();

    let event_builder =
        EventBuilder::text_note(&body).tags(nostru::body_hashtags(&body));

    user.send_builder(event_builder).await?;

    Ok(Response::temporary_redirect("/outbox"))
}

pub async fn draft_upload_file(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let draft_id = ctx.draft_id().ok_or(UserError::InvalidParams)?;

    let Some(titan_file) = ctx.titan_rsc else {
        return Ok(Response::temporary_failure("No file"));
    };

    match titan_to_blossom(titan_file, user.upload_keys.clone()).await {
        Ok(desc) => {
            let Ok(mut rtxn) = user.storage.get_read_txn() else {
                return Err(UserError::InvalidParams);
            };

            let Ok(mut content) =
                user.storage.draft_get_content(&draft_id, &mut rtxn)
            else {
                return Err(UserError::StorageError);
            };

            content.push_str(&format!("\n{}\n", desc.url));

            let _ = user.storage.draft_store(&draft_id, &content);

            Ok(Response::temporary_redirect(format!(
                "gemini://{}/draft/{}/edit",
                ctx.url.authority(),
                draft_id
            )))
        }
        Err(error) => Ok(Response::temporary_failure(format!(
            "Upload error: {error}"
        ))),
    }
}

pub async fn draft_mention(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let draft_id = ctx.draft_id().ok_or(UserError::InvalidParams)?;
    let kind = ctx.parameters.get("kind").ok_or(UserError::InvalidParams)?;

    match kind {
        "npub" => match ctx.parameters.get("data") {
            Some(npub) => {
                let pubkey = PublicKey::parse(npub)?;
                let Ok(_) = user.storage.draft_content_append(
                    &draft_id,
                    &format!("\n{}\n", pubkey.to_nostr_uri()?),
                ) else {
                    return Err(UserError::StorageError);
                };

                Ok(Response::temporary_redirect(format!(
                    "gemini://{}/draft/{}/edit",
                    ctx.url.authority(),
                    draft_id
                )))
            }
            None => Err(UserError::InvalidParams),
        },
        "npub_from_all" => {
            let metadata_events = user
                .client
                .database()
                .query(Filter::new().kind(Kind::Metadata).limit(100))
                .await?;

            Ok(Response::success(WindTemplate::render(
                NoteDraftMentionNpubTemplate { metadata_events },
            )))
        }
        "npub_from_follow_list" => {
            let contacts =
                user.client.get_contact_list(Duration::from_secs(5)).await?;

            let metadata_events = user
                .client
                .database()
                .query(
                    Filter::new()
                        .kind(Kind::Metadata)
                        .authors(contacts.into_iter().map(|c| c.public_key)),
                )
                .await?;

            Ok(Response::success(WindTemplate::render(
                NoteDraftMentionNpubTemplate { metadata_events },
            )))
        }

        "npub_manual" => match decode_query(&ctx) {
            Some(npub) => {
                Ok(Response::temporary_redirect(format!("npub/{npub}")))
            }
            None => Ok(Response::input(t!("type_in_npub"))),
        },
        _ => Err(UserError::InvalidParams),
    }
}
