use std::borrow::Cow;

use askama::Template;
use icu_datetime::{DateTimeFormatter, DateTimeFormatterPreferences};
use jiff::tz::TimeZone;
use jiff_icu::ConvertFrom;
use l10n_embed::Localize;
use l10n_embed_derive::localize;
use libpijul::Base32;
use pijul_extension::FileSystemRepository;
use pijul_extension::author::AuthorSource;
use pijul_extension::file_system::changes::{CreditSource, HunkDiff};
use pijul_extension::file_system::open_file::OpenFile;

use crate::vscode_sys;

#[localize("l10n/**/inline_credit/hover.ftl")]
enum HoverHeading {
    AbsoluteTimestamp,
    ChangeId,
}

#[localize("l10n/**/inline_credit/hover.ftl")]
struct HunkHeading {
    index: usize,
}

#[derive(askama::Template)]
#[template(path = "hover/diff.md", escape = "none")]
struct HunkHoverTemplate {
    heading: String,
    lines_added: Vec<String>,
    lines_removed: Vec<String>,
}

#[derive(askama::Template)]
#[template(path = "hover/tracked.html")]
struct TrackedHoverTemplate<'change_header> {
    title: &'change_header str,
    authors: String,
    relative_timestamp: String,
    absolute_timestamp_header: String,
    absolute_timestamp: String,
    change_id_header: String,
    // TODO: color/emphasize unique portion of ID (e.g. *ABC*DEFGHIJKL)
    change_id: String,
    description: Option<&'change_header str>,
    hunk_template: HunkHoverTemplate,
}

#[derive(askama::Template)]
#[template(path = "hover/untracked.html")]
struct UntrackedHoverTemplate {
    hunk_template: HunkHoverTemplate,
}

pub fn render<'env>(
    env: &'env napi::Env,
    localization_context: &l10n_embed::Context,
    repository: &FileSystemRepository,
    open_file: &OpenFile,
    credit_source: CreditSource,
) -> Result<vscode_sys::MarkdownString<'env>, napi::Error> {
    let active_hunk = repository
        .get_active_hunk(open_file, credit_source)
        .map_err(|error| napi::Error::from_reason(format!("Unable to get active hunk: {error}")))?;

    let mut hunk_heading_buffer = String::new();
    let hunk_heading = HunkHeading {
        index: active_hunk.index,
    };
    hunk_heading.localize(localization_context, &mut hunk_heading_buffer);

    let hunk_template = match active_hunk.diff {
        HunkDiff::TextChange {
            lines_added,
            lines_removed,
        } => HunkHoverTemplate {
            heading: hunk_heading_buffer,
            lines_added,
            lines_removed,
        },
    };

    let rendered_html_result = match credit_source {
        CreditSource::Tracked { vertex } => {
            let change = repository.get_change(vertex.change).map_err(|error| {
                napi::Error::from_reason(format!(
                    "Unable to get change for vertex {vertex:#?}: {error}"
                ))
            })?;

            let mut authors = String::new();
            let authors_list = l10n_embed::list::AndList::new(
                repository
                    .authors_for_change(&change.header)
                    .into_iter()
                    .map(|author_source| match author_source {
                        AuthorSource::Local(identity) | AuthorSource::Remote(identity) => {
                            Cow::from(format!(
                                "{} ({})",
                                identity.config.author.display_name,
                                identity.config.author.username
                            ))
                        }
                        AuthorSource::Unknown(public_key_hash) => Cow::from(public_key_hash),
                    })
                    .collect(),
            );
            authors_list.localize(localization_context, &mut authors);

            let mut relative_timestamp = String::new();
            change
                .header
                .timestamp
                .localize(localization_context, &mut relative_timestamp);

            let mut absolute_timestamp_header = String::new();
            let mut change_id_header = String::new();
            HoverHeading::AbsoluteTimestamp
                .localize(localization_context, &mut absolute_timestamp_header);
            HoverHeading::ChangeId.localize(localization_context, &mut change_id_header);

            // TODO: properly localize using l10n_embed
            let zoned_timestamp = change.header.timestamp.to_zoned(TimeZone::system());
            let icu_timestamp = icu_time::ZonedDateTime::<
                icu_calendar::Iso,
                icu_time::TimeZoneInfo<icu_time::zone::models::Full>,
            >::convert_from(&zoned_timestamp);
            let absolute_timestamp_formatter = DateTimeFormatter::try_new(
                DateTimeFormatterPreferences::from(&localization_context.locale),
                icu_datetime::fieldsets::YMDT::medium()
                    .with_zone(icu_datetime::fieldsets::zone::SpecificShort),
            )
            .map_err(|error| {
                napi::Error::from_reason(format!("Failed to create datetime formatter: {error}"))
            })?;
            let absolute_timestamp = absolute_timestamp_formatter
                .format(&icu_timestamp)
                .to_string();

            let hover_template = TrackedHoverTemplate {
                title: &change.header.message,
                authors,
                relative_timestamp,
                absolute_timestamp_header,
                absolute_timestamp,
                change_id_header,
                change_id: vertex.change.to_base32(),
                description: change.header.description.as_deref(),
                hunk_template,
            };

            hover_template.render()
        }
        CreditSource::Untracked { .. } => {
            let hover_template = UntrackedHoverTemplate { hunk_template };

            hover_template.render()
        }
    };

    let rendered_html = rendered_html_result.map_err(|error| {
        napi::Error::from_reason(format!("Failed to render hover message: {error}"))
    })?;

    let mut hover_message = vscode_sys::MarkdownString::new(env, &rendered_html, false)?;
    hover_message.set_support_html(true)?;

    Ok(hover_message)
}