use nostr::hashes::sha256::Hash as Sha256Hash;
use nostr_blossom::client::BlossomClient;
use nostr_sdk::{nips::nip65::extract_relay_list, prelude::*};

use crate::FlufferClient;
use crate::error::ServiceError;

use std::borrow::Cow;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use askama::Template;
use fluffer::GemBytes;
use fluskama::FluffTemplate;

#[derive(Template)]
#[template(path = "home.gmi", escape = "txt")]
struct GatewayHomePage {
    nsites: Events,
}

/// Fluffer route that catches all requests
pub async fn route_catchall(
    client: FlufferClient,
) -> Result<Vec<u8>, ServiceError> {
    let ctx = client.state.lock().await;

    let Some(domain) = client.url.domain() else {
        return gateway_home(&client, ctx.client.clone()).await;
    };

    let domain_parts: Vec<_> = domain.split(".").collect();
    let Some(first) = domain_parts.first() else {
        return gateway_home(&client, ctx.client.clone()).await;
    };

    match PublicKey::parse(first) {
        Ok(pubkey) => {
            let Ok(relay_list_events) = ctx
                .client
                .fetch_events(
                    Filter::new().kind(Kind::RelayList).author(pubkey),
                    Duration::from_secs(3),
                )
                .await
            else {
                return Err(ServiceError::FetchEventsFailure);
            };

            if let Some(event) = relay_list_events.first() {
                // TODO
                let _relays: Vec<_> = extract_relay_list(event).collect();
            };

            let filter = Filter::new()
                .kind(Kind::Custom(15128))
                .kind(Kind::Custom(35128))
                .author(pubkey);

            let Ok(events) = ctx.client.database().query(filter).await else {
                return Err(ServiceError::NostrDbError);
            };

            let Some(manifest) = events.first_owned() else {
                return Err(ServiceError::NoManifest);
            };

            handle_request(client.url.clone(), manifest).await
        }
        Err(_) => gateway_home(&client, ctx.client.clone()).await,
    }
}

pub async fn gateway_home(
    _fluffer_client: &FlufferClient,
    nostr_client: Arc<Client>,
) -> Result<Vec<u8>, ServiceError> {
    let nsites_filter = Filter::new()
        .kind(Kind::Custom(15128))
        .kind(Kind::Custom(35128));

    let Ok(nsites) = nostr_client
        .fetch_combined_events(nsites_filter, Duration::from_secs(5))
        .await
    else {
        return Err(ServiceError::FetchEventsFailure);
    };

    let fluff: FluffTemplate<GatewayHomePage> =
        FluffTemplate::from(GatewayHomePage { nsites });

    Ok(fluff.gem_bytes().await)
}

/// Find the blossom hash in the manifest event for a given [`Url`]
fn find_blossom_hash(
    manifest: &Event,
    orig_url: Url,
) -> Result<Sha256Hash, ServiceError> {
    let mut url = orig_url.clone();

    let index_html_url = orig_url.join("index.html")?;

    let Some(mut segments) = url.path_segments() else {
        return Err(ServiceError::InvalidUrl);
    };

    if segments.next_back() == Some("") {
        // Search for index.gmi if the URL's path ends with a /
        url = url.join("index.gmi")?;
    }

    // Look for path tag for the requested URL
    if let Some(sha) = manifest
        .tags
        .filter(TagKind::Custom(Cow::Borrowed("path")))
        .find(|tag| tag.content() == Some(url.path()))
        .and_then(|tag| tag.clone().to_vec().get(2).cloned())
    {
        Sha256Hash::from_str(&sha).map_err(|_| ServiceError::InvalidSha256)
    // Fallback: look for path tag for the index.html document
    } else if let Some(sha) = manifest
        .tags
        .filter(TagKind::Custom(Cow::Borrowed("path")))
        .find(|tag| tag.content() == Some(index_html_url.path()))
        .and_then(|tag| tag.clone().to_vec().get(2).cloned())
    {
        Sha256Hash::from_str(&sha).map_err(|_| ServiceError::InvalidSha256)
    } else {
        Err(ServiceError::ObjectNotFound)
    }
}

async fn handle_request(
    url: Url,
    manifest: Event,
) -> Result<Vec<u8>, ServiceError> {
    let Ok(hash) = find_blossom_hash(&manifest, url.clone()) else {
        return Err(ServiceError::ObjectNotFound);
    };

    let servers: Vec<_> = manifest
        .tags
        .filter(TagKind::Custom(Cow::Borrowed("server")))
        .filter_map(|tag| tag.content())
        .collect();

    let signer = Keys::generate();

    for server in servers {
        let Ok(url) = Url::parse(server) else {
            eprintln!("Invalid blossom server URL");
            continue;
        };

        let client = BlossomClient::new(url);

        match client.get_blob(hash, None, None, Some(&signer)).await {
            Ok(data) => {
                if let Ok(resp) = deliver_blob(data) {
                    return Ok(resp);
                } else {
                    continue;
                }
            }
            Err(err) => {
                eprintln!("Error fetching from blossom server: {err}");
            }
        }
    }

    Err(ServiceError::CannotFetchBlob)
}

fn deliver_blob(data: Vec<u8>) -> Result<Vec<u8>, ServiceError> {
    let mime = tree_magic_mini::from_u8(&data);

    match mime {
        "text/html" => {
            let html = str::from_utf8(&data)?;
            let md = html2md::parse_html(html);
            let gemtext = md2gemtext::convert(&md);

            Ok(format!("{} {}\r\n{}", 20, "text/gemini", gemtext).into_bytes())
        }
        "text/plain" => {
            let text = str::from_utf8(&data)?;
            Ok(format!("{} {}\r\n{}", 20, "text/gemini", text).into_bytes())
        }
        _ => {
            let mut resp = format!("{} {}\r\n", 20, mime).into_bytes();
            resp.extend(data);

            Ok(resp)
        }
    }
}
