use clap::ValueEnum;

use radicle::cob::Title;
use radicle::cob::store::access;
use radicle_ci_broker::msg::{Request, RequestBuilder, Response, RunId, RunResult};
use radicle_ci_broker::test::MockNode;

use git_ref_format_core::RefString;
use radicle::patch::{MergeTarget, Patches};
use radicle::storage::ReadRepository;

use super::*;

/// Output sample adapter messages.
#[derive(Parser)]
pub struct MessageCmd {
    /// What kind of message should be output?
    #[clap(long)]
    #[arg(value_enum)]
    kind: MessageKind,

    /// Info URL to include in a `triggered` message.
    #[clap(long)]
    info_url: Option<String>,
}

#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
enum MessageKind {
    Push,
    Patch,
    Triggered,
    Success,
    Failure,
}

impl Leaf for MessageCmd {
    fn run(&self, _args: &Args) -> Result<(), CibToolError> {
        let msg = match self.kind {
            MessageKind::Push => request_json(push()?)?,
            MessageKind::Patch => request_json(patch()?)?,
            MessageKind::Triggered => response_json(triggered(&self.info_url)?)?,
            MessageKind::Success => response_json(success()?)?,
            MessageKind::Failure => response_json(failure()?)?,
        };

        println!("{msg}");

        Ok(())
    }
}

fn request_json(req: Request) -> Result<String, MessageError> {
    req.to_json_pretty().map_err(MessageError::AdapterLib)
}

fn response_json(resp: Response) -> Result<String, MessageError> {
    resp.to_json_pretty().map_err(MessageError::AdapterLib)
}

fn push() -> Result<Request, MessageError> {
    let mock_node = MockNode::new().map_err(MessageError::Test)?;
    let profile = mock_node.profile().map_err(MessageError::Test)?;

    let project = mock_node.node().project();
    let (_, repo_head) = project.repo.head().map_err(MessageError::Repo)?;
    let cmt =
        radicle::test::fixtures::commit("my test commit", &[repo_head.into()], &project.backend);

    let ci_event = CiEvent::V1(CiEventV1::BranchCreated {
        from_node: *profile.id(),
        repo: project.id,
        branch: RefString::try_from(
            "refs/namespaces/$nid/refs/heads/master".replace("$nid", &profile.id().to_string()),
        )
        .map_err(MessageError::Git)?,
        tip: cmt,
    });

    let req = RequestBuilder::default()
        .profile(&profile)
        .ci_event(&ci_event)
        .build_trigger_from_ci_event()
        .map_err(MessageError::Message)?;
    Ok(req)
}

fn patch() -> Result<Request, MessageError> {
    let mock_node = MockNode::new()?;
    let profile = mock_node.profile()?;
    eprintln!("patch: C: {profile:?}");
    let signer = profile
        .signer()
        .map_err(|err| MessageError::Signer(Box::new(err)))?;

    let project = mock_node.node().project();
    let (_, repo_head) = project.repo.head()?;
    let cmt =
        radicle::test::fixtures::commit("my test commit", &[repo_head.into()], &project.backend);

    let mut patches = Patches::open(&project.repo, access::WriteAs::new(&signer))?;
    let mut cache = radicle::cob::cache::NoCache;
    let patch_cob = patches
        .create(
            Title::new("my patch title")?,
            "my patch description",
            MergeTarget::Delegates,
            repo_head,
            cmt,
            &[],
            &mut cache,
        )
        .map_err(MessageError::Patch)?;

    let ci_event = CiEvent::V1(CiEventV1::PatchCreated {
        from_node: *profile.id(),
        repo: project.id,
        patch: *patch_cob.id(),
        new_tip: cmt,
    });

    let req = RequestBuilder::default()
        .profile(&profile)
        .ci_event(&ci_event)
        .build_trigger_from_ci_event()?;
    eprintln!("patch: Z: {req:?}");
    Ok(req)
}

fn triggered(info_url: &Option<String>) -> Result<Response, MessageError> {
    let run_id = RunId::from("xyzzy");
    if let Some(url) = info_url {
        Ok(Response::triggered_with_url(run_id, url))
    } else {
        Ok(Response::triggered(run_id))
    }
}

fn success() -> Result<Response, MessageError> {
    Ok(Response::finished(RunResult::Success))
}

fn failure() -> Result<Response, MessageError> {
    Ok(Response::finished(RunResult::Failure))
}

#[derive(Debug, thiserror::Error)]
pub enum MessageError {
    #[error(transparent)]
    Test(#[from] Box<dyn std::error::Error>),

    #[error(transparent)]
    Repo(#[from] radicle::storage::RepositoryError),

    #[error(transparent)]
    Git(#[from] radicle::git::fmt::Error),

    #[error(transparent)]
    Message(#[from] radicle_ci_broker::msg::MessageError),

    #[error(transparent)]
    AdapterLib(#[from] radicle_ci_adapter::AdapterLibError),

    #[error(transparent)]
    Patch(#[from] radicle::patch::Error),

    #[error(transparent)]
    Title(#[from] radicle::cob::common::TitleError),

    #[error("failed to create a signer for Radicle repository for message")]
    Signer(#[source] Box<dyn std::error::Error + Send + 'static>),
}
