mod args;

use radicle::node::Handle;
use radicle::node::policy;
use radicle::node::policy::{Policy, Scope};
use radicle::{Node, prelude::*};
use radicle_term::Element as _;

use crate::commands::sync;
use crate::terminal as term;

pub use args::Args;

pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
    let profile = ctx.profile()?;
    let mut node = radicle::Node::new(profile.socket_from_env());

    match args::Operation::from(args) {
        args::Operation::List => seeding(&profile)?,
        args::Operation::Seed {
            rids,
            should_fetch,
            settings,
            scope,
        } => {
            let settings = settings.with_profile(&profile);
            for rid in rids {
                update(rid, scope, &mut node, &profile)?;

                if should_fetch
                    && node.is_running()
                    && let Err(e) = sync::fetch(rid, settings.clone(), &mut node, &profile)
                {
                    term::error(e);
                }
            }
        }
    }

    Ok(())
}

pub fn update(
    rid: RepoId,
    scope: Option<Scope>,
    node: &mut Node,
    profile: &Profile,
) -> Result<(), anyhow::Error> {
    let scope = match scope {
        Some(scope) => scope,
        None => profile
            .policies()?
            .seed_policy(&rid)?
            .scope()
            .unwrap_or(Scope::Followed),
    };

    let updated = profile.seed(rid, scope, node)?;
    let outcome = if updated { "updated" } else { "exists" };

    if let Ok(repo) = profile.storage.repository(rid)
        && repo.identity_doc()?.is_public()
    {
        profile.add_inventory(rid, node)?;
        term::success!("Inventory updated with {}", term::format::tertiary(rid));
    }

    term::success!(
        "Seeding policy {outcome} for {} with scope '{scope}'",
        term::format::tertiary(rid),
    );

    Ok(())
}

pub fn seeding(profile: &Profile) -> anyhow::Result<()> {
    let store = profile.policies()?;
    let storage = &profile.storage;
    let mut t = term::Table::new(term::table::TableOptions::bordered());

    t.header([
        term::format::default(String::from("Repository")),
        term::format::default(String::from("Name")),
        term::format::default(String::from("Policy")),
        term::format::default(String::from("Scope")),
    ]);
    t.divider();

    for policy in store.seed_policies()? {
        match policy {
            Ok(policy::SeedPolicy { rid, policy }) => {
                let id = rid.to_string();
                let name = storage
                    .repository(rid)
                    .and_then(|repo| repo.project().map(|proj| proj.name().to_string()))
                    .unwrap_or_default();
                let scope = policy
                    .scope()
                    .map_or(String::new(), |scope| scope.to_string());
                let policy = term::format::policy(&Policy::from(policy));

                t.push([
                    term::format::tertiary(id),
                    name.into(),
                    policy,
                    term::format::dim(scope),
                ])
            }
            Err(err) => {
                term::error(format!("Failed to read a seeding policy: {err}"));
            }
        }
    }

    if t.is_empty() {
        term::println(term::format::dim("No seeding policies to show."));
    } else {
        t.print();
    }

    Ok(())
}
