use std::time::SystemTime;

use ambient_ci::runlog::SynthLog;
use html_page::{Element, HtmlPage, Tag};
use radicle::git::Oid;
use time::{format_description::FormatItem, macros::format_description, OffsetDateTime};

use radicle_ci_broker::msg::{Patch, PatchEvent, PushEvent, Request, RunId, RunResult};

const CSS: &str = include_str!("style.css");

#[derive(Default)]
pub struct AdapterRunLog {
    project_name: Option<String>,
    trigger: Option<Request>,
    started: Option<SystemTime>,
    adapter_run_id: Option<RunId>,
    result: Option<RunResult>,
    ambient_stdout: Option<Vec<u8>>,
    ambient_stderr: Option<Vec<u8>>,
    ambient_run_log: Option<ambient_ci::runlog::RunLog>,
}

impl AdapterRunLog {
    pub fn set_project_name(&mut self, project_name: String) {
        self.project_name = Some(project_name);
    }

    pub fn set_trigger(&mut self, trigger: Request) {
        self.trigger = Some(trigger);
    }

    pub fn set_started(&mut self, ts: SystemTime) {
        self.started = Some(ts);
    }

    pub fn set_adapter_run_id(&mut self, run_id: RunId) {
        self.adapter_run_id = Some(run_id);
    }

    pub fn set_result(&mut self, result: RunResult) {
        self.result = Some(result);
    }

    pub fn set_ambient_output(&mut self, stdout: Vec<u8>, stderr: Vec<u8>) {
        self.ambient_stdout = Some(stdout);
        self.ambient_stderr = Some(stderr);
    }

    pub fn set_ambient_run_log(&mut self, log: ambient_ci::runlog::RunLog) {
        self.ambient_run_log = Some(log);
    }

    pub fn to_html(&self) -> HtmlPage {
        fn repo_id(v: &Option<Request>) -> String {
            v.as_ref()
                .map(|rid| rid.repo().to_string())
                .unwrap_or("<unknown repo id>".into())
        }

        fn repo_name(v: &Option<Request>) -> String {
            match v {
                Some(Request::Trigger { common, .. }) => common.repository.name.to_string(),
                _ => "<unknown repo name".to_string(),
            }
        }

        fn run_id(v: &Option<RunId>) -> String {
            v.as_ref()
                .map(|id| id.to_string())
                .unwrap_or("<unknown run id>".into())
        }

        fn result(v: &Option<RunResult>) -> String {
            v.as_ref()
                .map(|r| r.to_string().to_uppercase())
                .unwrap_or("<unknown run result>".into())
        }

        fn timestamp(when: &Option<SystemTime>) -> String {
            if let Some(when) = when.as_ref() {
                const DATE_FORMAT: &[FormatItem<'_>] =
                    format_description!("[year]-[month]-[day] [hour]:[minute]:[second]Z");

                let odt = OffsetDateTime::from(*when);
                odt.format(DATE_FORMAT)
                    .unwrap_or_else(|_| "<unknown timestamp".to_string())
            } else {
                "<unknown timestamp".into()
            }
        }

        fn trigger(v: &Option<Request>) -> String {
            if let Some(req) = v {
                if let Ok(s) = serde_json::to_string_pretty(req) {
                    return s;
                }
            }

            "<unknown repo id>".into()
        }

        fn commit_element(oid: &Oid) -> Element {
            if let Some((prefix, suffix)) = oid.to_string().split_at_checked(7) {
                Element::new(Tag::Span)
                    .with_child(
                        Element::new(Tag::Span)
                            .with_class("commit-id-prefix")
                            .with_text(prefix),
                    )
                    .with_child(
                        Element::new(Tag::Span)
                            .with_class("commit-id-suffix")
                            .with_text(suffix),
                    )
            } else {
                Element::new(Tag::Span)
                    .with_class("commit-id-prefix")
                    .with_text(oid.to_string())
            }
        }

        fn commit(v: &Option<Request>) -> Element {
            match v {
                Some(Request::Trigger {
                    push: Some(PushEvent { after, .. }),
                    ..
                }) => commit_element(after),

                Some(Request::Trigger {
                    patch:
                        Some(PatchEvent {
                            patch: Patch { after, .. },
                            ..
                        }),
                    ..
                }) => commit_element(after),

                _ => Element::new(Tag::Span)
                    .with_class("commit-id-unknown")
                    .with_text("<unknown commit>"),
            }
        }

        fn log(v: &Option<Vec<u8>>) -> String {
            if let Some(log) = v {
                if log.is_empty() {
                    "<empty log>".into()
                } else {
                    String::from_utf8_lossy(log).to_string()
                }
            } else {
                "<unknown log>".into()
            }
        }

        let title = format!("CI: {} {}", result(&self.result), repo_name(&self.trigger));

        let ended = SystemTime::now();
        let (started, duration) = if let Some(started) = &self.started {
            let duration = if let Ok(duration) = started.elapsed() {
                format!("{} seconds", duration.as_secs())
            } else {
                "<unknown duration>".to_string()
            };
            (timestamp(&self.started), duration)
        } else {
            (timestamp(&None), "<unknown duration>".to_string())
        };

        let toc = Element::new(Tag::Ul)
            .with_child(
                Element::new(Tag::Li).with_child(
                    Element::new(Tag::A)
                        .with_attribute("href", "#run_log")
                        .with_text("Run log"),
                ),
            )
            .with_child(
                Element::new(Tag::Li).with_child(
                    Element::new(Tag::A)
                        .with_attribute("href", "#triggered")
                        .with_text("Trigger message"),
                ),
            )
            .with_child(
                Element::new(Tag::Li).with_child(
                    Element::new(Tag::A)
                        .with_attribute("href", "#ambient_stdout")
                        .with_text("Ambient stdout"),
                ),
            )
            .with_child(
                Element::new(Tag::Li).with_child(
                    Element::new(Tag::A)
                        .with_attribute("href", "#ambient_stderr")
                        .with_text("Ambient stderr"),
                ),
            );

        HtmlPage::default()
            .with_head_element(Element::new(Tag::Title).with_text(&title))
            .with_head_element(Element::new(Tag::Style).with_text(CSS))
            .with_body_element(Element::new(Tag::H1).with_text(&title))
            .with_body_element(
                Element::new(Tag::Ul)
                    .with_child(
                        Element::new(Tag::Li).with_text("Result: ").with_child(
                            Element::new(Tag::Span)
                                .with_class(match &self.result {
                                    Some(RunResult::Success) => "result-success",
                                    Some(RunResult::Failure) => "result-failure",
                                    _ => "result-unknown",
                                })
                                .with_text(result(&self.result)),
                        ),
                    )
                    .with_child(
                        Element::new(Tag::Li)
                            .with_text("Repository ID: ")
                            .with_child(
                                Element::new(Tag::Span)
                                    .with_class("repo-id")
                                    .with_text(repo_id(&self.trigger)),
                            ),
                    )
                    .with_child(
                        Element::new(Tag::Li)
                            .with_text("Repository name: ")
                            .with_child(
                                Element::new(Tag::Span)
                                    .with_class("project-name")
                                    .with_text(repo_name(&self.trigger)),
                            ),
                    )
                    .with_child(
                        Element::new(Tag::Li)
                            .with_text("Adapter run ID: ")
                            .with_child(
                                Element::new(Tag::Span)
                                    .with_class("run-id")
                                    .with_text(run_id(&self.adapter_run_id)),
                            ),
                    )
                    .with_child(
                        Element::new(Tag::Li)
                            .with_text("Commit: ")
                            .with_child(commit(&self.trigger)),
                    )
                    .with_child(
                        Element::new(Tag::Li)
                            .with_text("Duration: ")
                            .with_text(duration.to_string())
                            .with_child(Element::new(Tag::Br))
                            .with_text(&started)
                            .with_text(" -- ")
                            .with_text(timestamp(&Some(ended))),
                    ),
            )
            .with_body_element(Element::new(Tag::H2).with_text("Table of Contents"))
            .with_body_element(toc)
            .with_body_element(
                Element::new(Tag::H2)
                    .with_attribute("id", "run_log")
                    .with_text("Run log"),
            )
            .with_body_element({
                if let Some(runlog) = &self.ambient_run_log {
                    let synth_log = SynthLog::new(runlog.msgs());
                    // FIXME: add console log
                    synth_log.to_html_messages()
                } else {
                    Element::new(Tag::Pre).with_text("No Ambient log available")
                }
            })
            .with_body_element(
                Element::new(Tag::H2)
                    .with_attribute("id", "triggered")
                    .with_text("Trigger message"),
            )
            .with_body_element(Element::new(Tag::Pre).with_text(trigger(&self.trigger)))
            .with_body_element(
                Element::new(Tag::H2)
                    .with_attribute("id", "ambient_stdout")
                    .with_text("Ambient stdout"),
            )
            .with_body_element(Element::new(Tag::Pre).with_text(log(&self.ambient_stdout)))
            .with_body_element(
                Element::new(Tag::H2)
                    .with_attribute("id", "ambient_stderr")
                    .with_text("Ambient stderr"),
            )
            .with_body_element(Element::new(Tag::Pre).with_text(log(&self.ambient_stderr)))
    }
}
