use std::str::FromStr;

use domain::{
    AgentIssueRef, AgentIssueRole, Issue, IssueActor, IssueError, IssueId, IssueLink,
    IssueLinkKind, IssueNumber, IssuePriority, IssueRef, IssueStatus, IssueVersion, MarkdownDoc,
};

fn issue_number(n: u64) -> IssueNumber {
    IssueNumber::new(n).unwrap()
}

fn issue() -> Issue {
    Issue::new(
        IssueId::new_random(),
        issue_number(7),
        "Fix boot reconcile",
        MarkdownDoc::new("Description"),
        IssueStatus::Open,
        IssuePriority::Medium,
        MarkdownDoc::new("Carnet"),
        vec![IssueLink {
            target: IssueRef::from(issue_number(8)),
            kind: IssueLinkKind::RelatesTo,
        }],
        vec![AgentIssueRef {
            agent_id: domain::AgentId::new_random(),
            role: AgentIssueRole::Assigned,
        }],
        IssueActor::User,
        1_000,
    )
    .unwrap()
}

#[test]
fn issue_ref_parses_and_formats_strict_hash_number() {
    let reference = IssueRef::from_str("#42").unwrap();

    assert_eq!(reference.number().get(), 42);
    assert_eq!(reference.to_string(), "#42");
    assert_eq!(
        IssueRef::from_str("42"),
        Err(IssueError::InvalidRef("42".into()))
    );
    assert_eq!(IssueRef::from_str("#0"), Err(IssueError::InvalidNumber));
    assert_eq!(
        IssueRef::from_str("#abc"),
        Err(IssueError::InvalidRef("#abc".into()))
    );
}

#[test]
fn issue_number_must_be_positive() {
    assert_eq!(IssueNumber::new(0), Err(IssueError::InvalidNumber));
}

#[test]
fn issue_title_must_not_be_empty() {
    let err = Issue::new(
        IssueId::new_random(),
        issue_number(1),
        "   ",
        MarkdownDoc::new("Description"),
        IssueStatus::Open,
        IssuePriority::Medium,
        MarkdownDoc::default(),
        Vec::new(),
        Vec::new(),
        IssueActor::User,
        1,
    )
    .unwrap_err();

    assert_eq!(err, IssueError::EmptyTitle);
}

#[test]
fn issue_rejects_self_link() {
    let err = Issue::new(
        IssueId::new_random(),
        issue_number(9),
        "Self link",
        MarkdownDoc::new("Description"),
        IssueStatus::Open,
        IssuePriority::Medium,
        MarkdownDoc::default(),
        vec![IssueLink {
            target: IssueRef::from(issue_number(9)),
            kind: IssueLinkKind::Blocks,
        }],
        Vec::new(),
        IssueActor::User,
        1,
    )
    .unwrap_err();

    assert_eq!(
        err,
        IssueError::SelfLink {
            reference: IssueRef::from(issue_number(9))
        }
    );
}

#[test]
fn issue_mutation_increments_version_and_updates_actor_time() {
    let issue = issue();
    assert_eq!(issue.version, IssueVersion::INITIAL);

    let updated = issue
        .mutate(IssueActor::System, 2_000, |i| {
            i.status = IssueStatus::InProgress;
        })
        .unwrap();

    assert_eq!(updated.version.get(), 2);
    assert_eq!(updated.status, IssueStatus::InProgress);
    assert_eq!(updated.updated_by, IssueActor::System);
    assert_eq!(updated.updated_at, 2_000);
}
