use std::time::Duration;

use crate::aska::WindTemplate;
use crate::emoji::*;
use crate::nostru;
use crate::rendering::{NoteRenderOptions, render_note};
use crate::routes::response::*;
use crate::user::CaracalUser;
use crate::util::{decode_query, extract_first_words, titan_to_blossom};

use crate::routes::{
    gem, resp_error_send_note, resp_note_prompt_text, resp_redirect_root,
};
use crate::urls::*;

use nostr_sdk::prelude::*;

use askama::Template;
use gnostr_types::{ContentSegment, ShatteredContent};
use hashtag::HashtagParser;
use mdiu::Block;
use rust_i18n::t;
use subslay::EmojiStylist;
use urlencoding::{decode, encode};
use windmark_titanesque::{
    context::{Parameters, RouteContext},
    response::Response,
};

use rand::distributions::{Alphanumeric, DistString};

#[derive(Template)]
#[template(path = "note.gmi", escape = "txt")]
pub struct NoteThreadTemplate {
    title: Option<String>,
    event: Event,
    note_blocks: Vec<Block>,
    replies_blocks: Option<Vec<Block>>,
}

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

#[derive(Template)]
#[template(path = "note_reaction_select.gmi", escape = "txt")]
pub struct NoteReactionSelect {
    event_id: EventId,
}

pub struct NoteContext {
    event: Event,
}

async fn note_context(
    params: Parameters,
    client: &Client,
) -> Option<NoteContext> {
    let Some(evid) = params.get("event_id") else {
        return None;
    };

    let Ok(event_id) = EventId::parse(evid) else {
        return None;
    };

    return match client.database().event_by_id(&event_id).await {
        Ok(ev) => {
            return if ev.is_some() {
                Some(NoteContext { event: ev.unwrap() })
            } else {
                None
            };
        }
        Err(_err) => None,
    };
}

fn body_hashtags(body: &str) -> Vec<Tag> {
    HashtagParser::new(body)
        .map(|ht| Tag::hashtag(&ht.text))
        .collect()
}

pub async fn post_note(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let mode = ctx.parameters.get("mode").unwrap_or("raw");

    let pow: u8 = match ctx.parameters.get("pow") {
        Some(value) => match value.to_string().parse::<u8>() {
            Ok(int) => int,
            Err(_error) => 0,
        },
        None => 0,
    };

    let url = ctx.url.clone();

    if let Some(text) = dec_urlq(&url) {
        let mut tags = Vec::new();
        let shattered_content = ShatteredContent::new(text.clone());
        for segment in shattered_content.segments.iter() {
            if let ContentSegment::NostrUrl(nostr_url) = segment
                && let Ok(n21) = Nip21::parse(&format!("{nostr_url}"))
                && let Nip21::Pubkey(pubk) = n21
            {
                tags.push(Tag::public_key(pubk));
            }
        }

        if let Some(url_hashtag) = ctx.parameters.get("hashtag") {
            // Hashtag in the URL => add a hashtag Tag to the list of tags
            tags.push(Tag::hashtag(url_hashtag));
        }

        tags.extend(body_hashtags(&text));

        let event_builder = match mode {
            "emojize" => {
                // Emojize with subslay
                match EmojiStylist::new() {
                    Ok(stylist) => {
                        EventBuilder::text_note(stylist.slay(&text).join(""))
                            .tags(tags)
                    }
                    Err(_) => EventBuilder::text_note(&text).tags(tags),
                }
            }
            _ => EventBuilder::text_note(&text).tags(tags),
        };

        let event = match pow {
            5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 => {
                event_builder.pow(pow).sign(&user.signer).await.unwrap()
            }
            0 => event_builder.sign(&user.signer).await.unwrap(),
            _ => todo!(),
        };

        if let Ok(output) = user.client.send_event(&event).await {
            Response::temporary_redirect(format!(
                "/note/{}/thread",
                output.id()
            ))
        } else {
            resp_error_send_note()
        }
    } else {
        resp_note_prompt_text()
    }
}

pub async fn note_thread(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let mut doc = vec![];
    let mut rblocks = vec![];
    let evid = ctx.parameters.get("event_id").unwrap();

    if let Ok(event_id) = EventId::parse(evid) {
        let e_filter = Filter::new()
            .kind(Kind::TextNote)
            .kind(Kind::LongFormTextNote)
            .id(event_id);
        let Ok(events) = user
            .client
            .fetch_combined_events(e_filter, Duration::from_secs(3))
            .await
        else {
            return Response::temporary_failure(t!("fetch_events_failed"));
        };

        let Some(event) = events.first() else {
            return Response::temporary_failure(t!("no_such_event"));
        };

        let rf_filter = Filter::new().kind(Kind::TextNote).event(event_id);
        let Ok(replies_events) = user
            .client
            .fetch_combined_events(rf_filter, Duration::from_secs(3))
            .await
        else {
            return Response::temporary_failure(t!(
                "fetch_note_replies_failed"
            ));
        };

        let usermeta =
            nostru::get_user_metadata(&user.client, event.pubkey).await;

        let title = extract_first_words(event, 16);

        let roptions = NoteRenderOptions::default()
            | NoteRenderOptions::PUBLIC_BOOKMARK
            | NoteRenderOptions::SHOW_HASHTAGS;

        match render_note(&user.client, event, usermeta.clone(), roptions).await
        {
            Ok(mut blocks) => {
                doc.append(&mut blocks);
            }
            Err(_err) => {}
        }

        for revent in replies_events {
            let rusermeta =
                nostru::get_user_metadata(&user.client, revent.pubkey).await;

            if let Ok(mut blks) =
                render_note(&user.client, &revent, rusermeta.clone(), roptions)
                    .await
            {
                rblocks.append(&mut blks);
            }
        }

        let tmpl = NoteThreadTemplate {
            title,
            event: event.clone(),
            note_blocks: doc,
            replies_blocks: Some(rblocks),
        };

        Response::success(WindTemplate::render(tmpl))
    } else {
        Response::temporary_failure(t!("invalid_event_id"))
    }
}

pub async fn note_reply(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    let url = ctx.url.clone();

    if let Some(text) = dec_urlq(&url) {
        let builder =
            EventBuilder::text_note_reply(&text, &note_ctx.event, None, None)
                .tags(body_hashtags(&text));

        match user.send_builder(builder).await {
            Ok(output) => Response::temporary_redirect(format!(
                "/note/{}/thread",
                output.id()
            )),
            Err(err) => Response::temporary_failure(format!("{err}")),
        }
    } else {
        resp_note_prompt_text()
    }
}

pub async fn note_repost(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    let builder = EventBuilder::repost(&note_ctx.event, None);

    match user.send_builder(builder).await {
        Ok(_) => Response::temporary_redirect(format!(
            "/note/{}/thread",
            note_ctx.event.id
        )),
        Err(_) => resp_error_send_note(),
    }
}

/// React to a note (custom input from user)
pub async fn note_react_custom(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(emoji) = decode_query(&ctx) else {
        return Response::input(t!("input_reaction_emoji"));
    };

    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    let builder = EventBuilder::reaction(&note_ctx.event, emoji);

    match user.send_builder(builder).await {
        Ok(_) => Response::temporary_redirect(format!(
            "/note/{}/thread",
            note_ctx.event.id
        )),
        Err(err) => {
            Response::temporary_failure(format!("Send event failed: {err}"))
        }
    }
}

/// React to a note, using a template that lets the user pick an emoji
pub async fn note_react_select(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    Response::success(WindTemplate::render(NoteReactionSelect {
        event_id: note_ctx.event.id,
    }))
}

/// Sent an event deletion request for a note
pub async fn note_delete(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    // Ask for confirmation
    if let Some(input_text) = dec_urlq(&ctx.url) {
        let input_text = input_text.to_lowercase();
        let yes = t!("yes").to_lowercase();

        if input_text != yes {
            return Response::input(t!(
                "delete_note_confirm",
                confirm = t!("yes_positive")
            ));
        }
    } else {
        return Response::input(t!(
            "delete_note_confirm",
            confirm = t!("yes_positive")
        ));
    };

    let builder =
        EventBuilder::delete(EventDeletionRequest::new().id(note_ctx.event.id));

    match user.send_builder(builder).await {
        Ok(_) => Response::temporary_redirect("/"),
        Err(err) => {
            Response::temporary_failure(format!("Send event failed: {err}"))
        }
    }
}

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

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

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

    let Some(draft_id) = ctx.parameters.get("draft_id") else {
        return resp_invalid_params();
    };

    if let Some(titan) = ctx.titan_rsc {
        let Ok(rsc_text) = titan.content_text() else {
            return 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 resp_invalid_params();
    };

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

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

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

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

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

    let Some(draft_id) = ctx.parameters.get("draft_id") else {
        return resp_invalid_params();
    };

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

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

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

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

pub async fn draft_post(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(draft_id) = ctx.parameters.get("draft_id") else {
        return resp_invalid_params();
    };

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

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

    let body = draft.render_text();

    let event: Event = EventBuilder::text_note(&body)
        .tags(body_hashtags(&body))
        .sign(&user.signer)
        .await
        .unwrap();

    let Ok(_) = user.client.send_event(&event).await else {
        return resp_error_send_note();
    };

    resp_redirect_root()
}

pub async fn draft_upload_file(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(draft_id) = ctx.parameters.get("draft_id") else {
        return resp_invalid_params();
    };

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

    match titan_to_blossom(titan_file, user.signer.clone()).await {
        Ok(desc) => {
            let Ok(mut rtxn) = user.storage.get_read_txn() else {
                return resp_invalid_params();
            };

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

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

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

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