use std::error::Error;

use gotcha_gitea::{
    ActionJobLog, ActionRunDetails, Client,
    models::{ActionWorkflow, ActionWorkflowJob, ActionWorkflowRun},
};

use crate::{
    config::{RepositoryScope, Selection},
    print_table,
};

const ACTION_HELP: &str = "\
Usage: gotcha action workflow|run SUBCOMMAND

Workflow subcommands:
  workflow list [OWNER/REPOSITORY]
                                  List workflows
  workflow dispatch WORKFLOW REF [OWNER/REPOSITORY]
                                  Dispatch a workflow on a Git reference

Run subcommands:
  run list [OWNER/REPOSITORY]      List workflow runs
  run show ID [OWNER/REPOSITORY]   Show a run and its jobs
  run logs ID [OWNER/REPOSITORY]   Print every job log for a run";

const WORKFLOW_HELP: &str = "\
Usage: gotcha action workflow SUBCOMMAND

Subcommands:
  list [OWNER/REPOSITORY]
  dispatch WORKFLOW REF [OWNER/REPOSITORY]";

const RUN_HELP: &str = "\
Usage: gotcha action run SUBCOMMAND

Subcommands:
  list [OWNER/REPOSITORY]
  show ID [OWNER/REPOSITORY]
  logs ID [OWNER/REPOSITORY]";

pub async fn run(
    command: &[String],
    selection: &Selection,
    client: &Client,
) -> Result<bool, Box<dyn Error>> {
    match command {
        [domain, group, action]
            if domain == "action" && group == "workflow" && action == "list" =>
        {
            print_workflows(&client.action_workflows(&scope(selection, None)?).await?);
        }
        [domain, group, action, repository]
            if domain == "action" && group == "workflow" && action == "list" =>
        {
            print_workflows(
                &client
                    .action_workflows(&scope(selection, Some(repository))?)
                    .await?,
            );
        }
        [domain, group, action, workflow, reference]
            if domain == "action" && group == "workflow" && action == "dispatch" =>
        {
            dispatch(client, &scope(selection, None)?, workflow, reference).await?;
        }
        [domain, group, action, workflow, reference, repository]
            if domain == "action" && group == "workflow" && action == "dispatch" =>
        {
            dispatch(
                client,
                &scope(selection, Some(repository))?,
                workflow,
                reference,
            )
            .await?;
        }
        [domain, group, action] if domain == "action" && group == "run" && action == "list" => {
            print_runs(&client.action_runs(&scope(selection, None)?).await?);
        }
        [domain, group, action, repository]
            if domain == "action" && group == "run" && action == "list" =>
        {
            print_runs(
                &client
                    .action_runs(&scope(selection, Some(repository))?)
                    .await?,
            );
        }
        [domain, group, action, id] if domain == "action" && group == "run" && action == "show" => {
            print_run(
                &client
                    .action_run_details(&scope(selection, None)?, number(id)?)
                    .await?,
            );
        }
        [domain, group, action, id, repository]
            if domain == "action" && group == "run" && action == "show" =>
        {
            print_run(
                &client
                    .action_run_details(&scope(selection, Some(repository))?, number(id)?)
                    .await?,
            );
        }
        [domain, group, action, id] if domain == "action" && group == "run" && action == "logs" => {
            print_logs(
                number(id)?,
                &client
                    .action_run_logs(&scope(selection, None)?, number(id)?)
                    .await?,
            );
        }
        [domain, group, action, id, repository]
            if domain == "action" && group == "run" && action == "logs" =>
        {
            print_logs(
                number(id)?,
                &client
                    .action_run_logs(&scope(selection, Some(repository))?, number(id)?)
                    .await?,
            );
        }
        [domain, ..] if domain == "action" => return Ok(false),
        _ => return Ok(false),
    }
    Ok(true)
}

pub fn domain_help(domain: &str) -> Option<&'static str> {
    (domain == "action").then_some(ACTION_HELP)
}

pub fn subcommand_help(domain: &str, command: &str) -> Option<&'static str> {
    match (domain, command) {
        ("action", "workflow") => Some(WORKFLOW_HELP),
        ("action", "run") => Some(RUN_HELP),
        _ => None,
    }
}

fn scope(selection: &Selection, value: Option<&str>) -> Result<RepositoryScope, Box<dyn Error>> {
    match value {
        Some(value) => Ok(RepositoryScope::parse(value)?),
        None => selection.repository.clone().ok_or_else(|| {
            "cannot infer a repository; run inside one or pass OWNER/REPOSITORY".into()
        }),
    }
}

fn number(value: &str) -> Result<i64, Box<dyn Error>> {
    let number = value.parse()?;
    if number < 1 {
        return Err("run ID must be a positive integer".into());
    }
    Ok(number)
}

async fn dispatch(
    client: &Client,
    repository: &RepositoryScope,
    workflow: &str,
    reference: &str,
) -> Result<(), Box<dyn Error>> {
    client
        .dispatch_action_workflow(repository, workflow, reference)
        .await?;
    println!("Dispatched workflow {workflow} on {reference}.");
    Ok(())
}

fn print_workflows(workflows: &[ActionWorkflow]) {
    print_table(
        &[("ID", 40), ("STATE", 12), ("NAME", 50), ("PATH", 70)],
        workflows
            .iter()
            .map(|workflow| {
                vec![
                    workflow.id.as_deref().unwrap_or("unknown").into(),
                    workflow.state.as_deref().unwrap_or("unknown").into(),
                    workflow.name.as_deref().unwrap_or("").into(),
                    workflow.path.as_deref().unwrap_or("").into(),
                ]
            })
            .collect(),
    );
}

fn print_runs(runs: &[ActionWorkflowRun]) {
    print_table(
        &[
            ("ID", 12),
            ("RUN", 8),
            ("STATUS", 14),
            ("RESULT", 14),
            ("EVENT", 20),
            ("BRANCH", 30),
            ("WORKFLOW", 60),
        ],
        runs.iter()
            .map(|run| {
                vec![
                    run.id.unwrap_or_default().to_string(),
                    run.run_number.unwrap_or_default().to_string(),
                    run.status.as_deref().unwrap_or("unknown").into(),
                    run.conclusion.as_deref().unwrap_or("-").into(),
                    run.event.as_deref().unwrap_or("unknown").into(),
                    run.head_branch.as_deref().unwrap_or("-").into(),
                    run.path.as_deref().unwrap_or("").into(),
                ]
            })
            .collect(),
    );
}

fn print_run(details: &ActionRunDetails) {
    let run = &details.run;
    println!(
        "Run {} #{} [{} / {}] {}",
        run.id.unwrap_or_default(),
        run.run_number.unwrap_or_default(),
        run.status.as_deref().unwrap_or("unknown"),
        run.conclusion.as_deref().unwrap_or("-"),
        run.display_title.as_deref().unwrap_or("")
    );
    field("workflow", run.path.as_deref());
    field("event", run.event.as_deref());
    field("branch", run.head_branch.as_deref());
    field("commit", run.head_sha.as_deref());
    field("started", run.started_at.as_deref());
    field("completed", run.completed_at.as_deref());
    field("url", run.html_url.as_deref());
    print_jobs(&details.jobs);
}

fn print_jobs(jobs: &[ActionWorkflowJob]) {
    println!();
    print_table(
        &[
            ("JOB", 12),
            ("STATUS", 14),
            ("RESULT", 14),
            ("RUNNER", 30),
            ("NAME", 60),
        ],
        jobs.iter()
            .map(|job| {
                vec![
                    job.id.unwrap_or_default().to_string(),
                    job.status.as_deref().unwrap_or("unknown").into(),
                    job.conclusion.as_deref().unwrap_or("-").into(),
                    job.runner_name.as_deref().unwrap_or("-").into(),
                    job.name.as_deref().unwrap_or("").into(),
                ]
            })
            .collect(),
    );
}

fn print_logs(run: i64, logs: &[ActionJobLog]) {
    if logs.is_empty() {
        println!("No jobs found for run {run}.");
        return;
    }
    for (index, log) in logs.iter().enumerate() {
        if index > 0 {
            println!();
        }
        println!(
            "== {} ({}) ==",
            log.job.name.as_deref().unwrap_or("job"),
            log.job.id.unwrap_or_default()
        );
        print!("{}", log.text);
        if !log.text.ends_with('\n') {
            println!();
        }
    }
}

fn field(name: &str, value: Option<&str>) {
    if let Some(value) = value.filter(|value| !value.is_empty()) {
        println!("{name}: {value}");
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validates_run_ids_and_exposes_nested_help() {
        assert!(number("0").is_err());
        assert_eq!(number("95").unwrap(), 95);
        assert!(domain_help("action").unwrap().contains("workflow dispatch"));
        assert!(
            subcommand_help("action", "run")
                .unwrap()
                .contains("logs ID")
        );
    }
}
