mod bookmarks;
mod community;
mod contacts;
mod dm;
mod feed;
mod filters;
mod follow_packs;
mod followers;
mod following;
mod hashtags;
mod inbox;
mod manual;
mod mute;
mod nostr_db;
mod notes;
mod nsite;
mod outbox;
mod polls;
mod profile;
mod remote_signer;
mod response;
mod search;
mod sec;
mod settings;
mod www;

mod route_prelude;

pub use response::*;

pub use bookmarks::{
    bookmark_public_add, public_bookmarks, public_bookmarks_delete_event,
};
pub use community::{
    nostriches_list_all, nostriches_list_recent, nostriches_search,
};
pub use contacts::contact_list_index;
pub use dm::{dm_all, person_dm_conversation, person_send_dm};
pub use feed::show_feed;
pub use follow_packs::{
    follow_pack_action, follow_pack_show, follow_packs_index,
};
pub use followers::person_followers_list;
pub use following::{follow, person_follow, person_unfollow};
pub use hashtags::{hashtags_index, notes_by_hashtag};
pub use inbox::inbox;
pub use manual::manual_root;
pub use mute::{person_mute, person_unmute};
pub use nostr_db::db_purge;
pub use notes::{
    draft_body_append, draft_edit, draft_longform_body_append,
    draft_longform_delete_last_line, draft_longform_edit, draft_longform_new,
    draft_longform_new_with_id, draft_longform_post, draft_longform_preview,
    draft_longform_set, draft_mention, draft_new, draft_post,
    draft_upload_file, note_delete, note_react_custom, note_react_select,
    note_reply, note_repost, note_republish, note_thread, post_note,
};
pub use nsite::nsites_index;
pub use outbox::outbox;
pub use polls::{poll_details, poll_respond, polls_list};
pub use profile::{
    profile_edit, profile_index, profile_init_reset, profile_titan_edit,
    profile_upload,
};
pub use remote_signer::{
    nostr_connect_bunker, nostr_connect_client_initiated, nostr_connect_index,
};
pub use search::search_content;
pub use sec::private_key_export;
pub use settings::{
    relay_modify, relays_add_from_popular, relays_settings, settings_index,
    ui_settings, ui_settings_chlocale,
};
pub use www::route_www_get;

use crate::aska::WindTemplate;
use crate::emoji::*;
use crate::nostru;
use crate::rendering;
use crate::user::{CaracalUser, UserError, lookup_user, lookup_user_mut};
use crate::util::fingerprint;

use urlencoding::{decode, encode};

use askama::Template;
use colored::*;

use windmark_titanesque::{context::RouteContext, response::Response};

use chrono::DateTime;
use nostr_sdk::prelude::*;

use mdiu::Gemtext;
use mdiu::{Block, Markup};

use openssl::x509::X509;

use rust_i18n::t;

use std::future::IntoFuture;

rust_i18n::i18n!("locales");

#[derive(Template, Clone)]
#[template(path = "index.gmi", escape = "txt")]
struct IndexTemplate<'a> {
    public_key: &'a PublicKey,
    connect_uri: Option<&'a NostrConnectURI>,
    connect_relays: Option<Vec<String>>,
    profile_metadata: Option<Metadata>,
}

#[derive(Template, Clone)]
#[template(path = "index_nocert.gmi", escape = "txt")]
struct IndexNoCertTemplate {}

#[derive(Template, Clone)]
#[template(path = "about.gmi", escape = "txt")]
struct AboutTemplate {}

#[derive(Template)]
#[template(path = "person_index.gmi", escape = "txt")]
pub struct PersonIndexTemplate {
    npub: String,
    metadata: Option<Metadata>,
    notes_blocks: Option<Vec<Block>>,
    payment_targets: Option<Event>,
}

pub fn gem(blocks: &[Block]) -> String {
    <Gemtext>::markup(blocks)
}

/// Formats a [`Timestamp`] to a human format
pub fn humanize_ts(ts: &Timestamp) -> String {
    if let Ok(dt) =
        DateTime::parse_from_rfc3339(ts.to_human_datetime().as_str())
    {
        format!("{}", dt.format("%a %b %e %Y, %T"))
    } else {
        String::from("Invalid date")
    }
}

fn log_response(resp: &Response) {
    match resp.status {
        10 => println!("{}", resp.status.to_string().blue()),
        20..=22 => println!("{}", resp.status.to_string().green()),
        _ => println!("{}", resp.status.to_string().red()),
    }
}

/// Main route handler
/// If the user has a certificate, call the route
/// Otherwise, redirect to the "no cert" page to tell the user to create a cert
pub async fn guardian<R>(
    ctx: RouteContext,
    mut handler: impl FnMut(RouteContext, &'static mut CaracalUser) -> R
    + Send
    + Sync
    + 'static,
) -> Response
where
    R: IntoFuture<Output = Response> + Send + 'static,
{
    if let Some(cert) = &ctx.certificate {
        let Some(fingerprint) = fingerprint(cert) else {
            return Response::temporary_failure(
                "Could not load your certificate",
            );
        };

        // Print the request as gemtext
        println!("{} {}", "From:".red(), fingerprint.yellow());
        println!("{} {}", "=>".yellow(), ctx.url.path().blue());

        let Some(user) = lookup_user_mut(fingerprint) else {
            return Response::temporary_failure("Cannot find user");
        };

        let signer = user.client.signer().await;

        if signer.is_err() && !ctx.url.path().starts_with("/connect") {
            // No signer: render the nostr connect homepage (no redirect)
            nostr_connect_index(ctx, user).await
        } else {
            // A signer is set
            let resp = handler(ctx, user).await;

            log_response(&resp);

            resp
        }
    } else {
        Response::success(WindTemplate::render(IndexNoCertTemplate {}))
    }
}

pub async fn route_handler_get_response<R>(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
    mut handler: impl FnMut(RouteContext, &'static mut CaracalUser) -> R
    + Send
    + Sync
    + 'static,
) -> Response
where
    R: IntoFuture<Output = Result<Response, UserError>> + Send + 'static,
{
    match handler(ctx, user).await {
        Ok(resp) => {
            log_response(&resp);

            resp
        }
        Err(error) => Response::temporary_failure(format!("{error}")),
    }
}

/// Same as guardian, but calls route handlers that return a Result
pub async fn guardian_r<R>(
    ctx: RouteContext,
    handler: impl FnMut(RouteContext, &'static mut CaracalUser) -> R
    + Send
    + Sync
    + 'static,
) -> Response
where
    R: IntoFuture<Output = Result<Response, UserError>> + Send + 'static,
{
    if let Some(cert) = &ctx.certificate {
        let Some(fingerprint) = fingerprint(cert) else {
            return Response::temporary_failure(
                "Could not load your certificate",
            );
        };

        // Print the request as gemtext
        println!("{} {}", "From:".red(), fingerprint.yellow());
        println!("{} {}", "=>".yellow(), ctx.url.path().blue());

        let Some(user) = lookup_user_mut(fingerprint) else {
            return Response::temporary_failure("Cannot find user");
        };

        let signer = user.client.signer().await;

        if signer.is_err() && !ctx.url.path().starts_with("/connect") {
            nostr_connect_index(ctx, user).await
        } else {
            route_handler_get_response(ctx, user, handler).await
        }
    } else {
        Response::success(WindTemplate::render(IndexNoCertTemplate {}))
    }
}

pub async fn ugate(certificate: Option<X509>) -> Option<&'static CaracalUser> {
    if let Some(cert) = certificate {
        match fingerprint(&cert) {
            Some(fp) => lookup_user(fp),
            None => None,
        }
    } else {
        None
    }
}

pub async fn ugate_mut(
    certificate: Option<X509>,
) -> Option<&'static mut CaracalUser> {
    if let Some(cert) = certificate {
        match fingerprint(&cert) {
            Some(fp) => lookup_user_mut(fp),
            None => None,
        }
    } else {
        None
    }
}

pub async fn about(_ctx: RouteContext) -> Response {
    Response::success(WindTemplate::render(AboutTemplate {}))
}

pub async fn index(
    _ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Ok(signer) = user.client.signer().await else {
        return Response::temporary_failure("Signer error!");
    };

    match signer.get_public_key().await {
        Ok(pubkey) => {
            let profile_metadata =
                nostru::get_user_metadata(&user.client, pubkey).await;

            let connect_relays = user.connect_uri.as_ref().map(|uri| {
                uri.relays().iter().map(|r| r.to_string()).collect()
            });

            Response::success(WindTemplate::render(IndexTemplate {
                public_key: &pubkey,
                profile_metadata,
                connect_uri: user.connect_uri.as_ref(),
                connect_relays,
            }))
        }
        Err(_) => {
            // TODO: handle error
            Response::temporary_redirect("/connect")
        }
    }
}

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

    let Some(npub) = params.get("npub") else {
        return resp_invalid_params();
    };

    let Ok(pubkey) = PublicKey::parse(npub) else {
        return resp_invalid_params();
    };

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

    let _ = user
        .client
        .subscribe(nostru::notes_filter().author(pubkey).limit(500), None)
        .await;

    // Payment targets
    let Ok(pt_events) = user
        .fetch_quick(
            Filter::new()
                .kind(Kind::Custom(10133))
                .author(pubkey)
                .limit(1),
        )
        .await
    else {
        return Response::temporary_failure("Could not fetch payment targets");
    };

    let payment_targets = pt_events.first_owned();

    let notes: Option<Vec<Block>> = match user
        .client
        .database()
        .query(nostru::notes_filter().author(pubkey).limit(300))
        .await
    {
        Ok(events) => (rendering::render_notes(
            events.to_vec(),
            &user.client,
            None,
            None,
            rendering::NoteRenderOptions::default(),
        )
        .await)
            .ok(),
        Err(_error) => None,
    };

    let template = PersonIndexTemplate {
        npub: pubkey.to_bech32().unwrap(),
        metadata: usermeta,
        notes_blocks: notes,
        payment_targets,
    };

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

pub async fn connect_with_keys(
    _ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let Ok(mut rtxn) = user.storage.get_read_txn() else {
        return Err(UserError::StorageError);
    };

    match user.storage.get_default_keys(&mut rtxn) {
        Ok(keys) => {
            user.change_signer(keys).await?;

            Ok(Response::temporary_redirect("/"))
        }
        Err(_error) => match user.storage.create_default_keys() {
            Ok(keys) => {
                user.change_signer(keys).await?;

                Ok(Response::temporary_redirect("/"))
            }
            Err(_error) => Err(UserError::StorageError),
        },
    }
}
