//! L8 tests for the Git DTO (de)serialisation contract: camelCase on the
//! wire, transparent list shapes, `From<…Output>` conversions, and request
//! DTO deserialisation.

use app_tauri_lib::dto::{
    GitBranchesDto, GitCheckoutRequestDto, GitCommitDto, GitCommitListDto, GitCommitRequestDto,
    GitFileStatusDto, GitStageRequestDto, GitStatusListDto, GraphCommitDto, GraphCommitListDto,
};
use application::{
    GitBranchesOutput, GitCommitOutput, GitGraphOutput, GitLogOutput, GitStatusOutput,
};
use domain::ports::{GitCommitInfo, GitFileStatus, GraphCommit};
use serde_json::json;
use uuid::Uuid;

// ---------------------------------------------------------------------------
// GitFileStatusDto serialisation
// ---------------------------------------------------------------------------

#[test]
fn git_file_status_dto_serialises_camelcase() {
    let dto = GitFileStatusDto {
        path: "src/main.rs".to_owned(),
        staged: true,
    };
    let v = serde_json::to_value(&dto).unwrap();
    assert_eq!(v["path"], "src/main.rs");
    assert_eq!(v["staged"], true);
    // no snake_case leak — both fields are single words so no renaming needed,
    // but verify no extra/unexpected keys.
    assert!(v.get("path").is_some());
    assert!(v.get("staged").is_some());
}

#[test]
fn git_file_status_from_domain_type() {
    let domain = GitFileStatus {
        path: "README.md".to_owned(),
        staged: false,
    };
    let dto = GitFileStatusDto::from(domain);
    assert_eq!(dto.path, "README.md");
    assert!(!dto.staged);
}

// ---------------------------------------------------------------------------
// GitStatusListDto — transparent array
// ---------------------------------------------------------------------------

#[test]
fn git_status_list_dto_is_transparent_array() {
    let out = GitStatusOutput {
        entries: vec![
            GitFileStatus {
                path: "a.rs".to_owned(),
                staged: true,
            },
            GitFileStatus {
                path: "b.rs".to_owned(),
                staged: false,
            },
        ],
    };
    let dto = GitStatusListDto::from(out);
    let v = serde_json::to_value(&dto).unwrap();
    let arr = v.as_array().expect("transparent array");
    assert_eq!(arr.len(), 2);
    assert_eq!(arr[0]["path"], "a.rs");
    assert_eq!(arr[0]["staged"], true);
    assert_eq!(arr[1]["path"], "b.rs");
    assert_eq!(arr[1]["staged"], false);
}

// ---------------------------------------------------------------------------
// GitCommitDto serialisation & From impls
// ---------------------------------------------------------------------------

#[test]
fn git_commit_dto_serialises_camelcase() {
    let dto = GitCommitDto {
        hash: "abc123".to_owned(),
        summary: "feat: add git support".to_owned(),
    };
    let v = serde_json::to_value(&dto).unwrap();
    assert_eq!(v["hash"], "abc123");
    assert_eq!(v["summary"], "feat: add git support");
}

#[test]
fn git_commit_dto_from_commit_info() {
    let info = GitCommitInfo {
        hash: "deadbeef".to_owned(),
        summary: "fix: something".to_owned(),
    };
    let dto = GitCommitDto::from(info);
    assert_eq!(dto.hash, "deadbeef");
    assert_eq!(dto.summary, "fix: something");
}

#[test]
fn git_commit_dto_from_commit_output() {
    let out = GitCommitOutput {
        commit: GitCommitInfo {
            hash: "cafebabe".to_owned(),
            summary: "chore: cleanup".to_owned(),
        },
    };
    let dto = GitCommitDto::from(out);
    assert_eq!(dto.hash, "cafebabe");
    assert_eq!(dto.summary, "chore: cleanup");
}

// ---------------------------------------------------------------------------
// GitCommitListDto — transparent array
// ---------------------------------------------------------------------------

#[test]
fn git_commit_list_dto_is_transparent_array() {
    let out = GitLogOutput {
        commits: vec![
            GitCommitInfo {
                hash: "aaa".to_owned(),
                summary: "first".to_owned(),
            },
            GitCommitInfo {
                hash: "bbb".to_owned(),
                summary: "second".to_owned(),
            },
        ],
    };
    let dto = GitCommitListDto::from(out);
    let v = serde_json::to_value(&dto).unwrap();
    let arr = v.as_array().expect("transparent array");
    assert_eq!(arr.len(), 2);
    assert_eq!(arr[0]["hash"], "aaa");
    assert_eq!(arr[0]["summary"], "first");
}

// ---------------------------------------------------------------------------
// GitBranchesDto serialisation (incl. current: null)
// ---------------------------------------------------------------------------

#[test]
fn git_branches_dto_serialises_with_current() {
    let out = GitBranchesOutput {
        branches: vec!["main".to_owned(), "feature/x".to_owned()],
        current: Some("main".to_owned()),
    };
    let dto = GitBranchesDto::from(out);
    let v = serde_json::to_value(&dto).unwrap();
    assert_eq!(v["branches"][0], "main");
    assert_eq!(v["branches"][1], "feature/x");
    assert_eq!(v["current"], "main");
}

#[test]
fn git_branches_dto_serialises_null_current() {
    let out = GitBranchesOutput {
        branches: vec![],
        current: None,
    };
    let dto = GitBranchesDto::from(out);
    let v = serde_json::to_value(&dto).unwrap();
    assert!(v["current"].is_null(), "current must be null when None");
}

// ---------------------------------------------------------------------------
// GraphCommitDto serialisation & From impls
// ---------------------------------------------------------------------------

#[test]
fn graph_commit_dto_serialises_camelcase() {
    let commit = GraphCommit {
        hash: "abc123".to_owned(),
        summary: "feat: graph".to_owned(),
        parents: vec!["deadbeef".to_owned()],
        refs: vec!["main".to_owned(), "tag: v1.0".to_owned()],
        author: "Alice".to_owned(),
        timestamp: 1_700_000_000,
    };
    let dto = GraphCommitDto::from(commit);
    let v = serde_json::to_value(&dto).unwrap();
    assert_eq!(v["hash"], "abc123");
    assert_eq!(v["summary"], "feat: graph");
    assert_eq!(v["parents"][0], "deadbeef");
    assert_eq!(v["refs"][0], "main");
    assert_eq!(v["refs"][1], "tag: v1.0");
    assert_eq!(v["author"], "Alice");
    assert_eq!(v["timestamp"], 1_700_000_000_i64);
    // No snake_case leaks for multi-word fields — none here but verify shape.
    assert!(v.get("hash").is_some());
}

#[test]
fn graph_commit_list_dto_is_transparent_array() {
    let out = GitGraphOutput {
        commits: vec![
            GraphCommit {
                hash: "aaa".to_owned(),
                summary: "first".to_owned(),
                parents: vec![],
                refs: vec!["main".to_owned()],
                author: "Bob".to_owned(),
                timestamp: 1000,
            },
            GraphCommit {
                hash: "bbb".to_owned(),
                summary: "second".to_owned(),
                parents: vec!["aaa".to_owned()],
                refs: vec![],
                author: "Bob".to_owned(),
                timestamp: 999,
            },
        ],
    };
    let dto = GraphCommitListDto::from(out);
    let v = serde_json::to_value(&dto).unwrap();
    let arr = v.as_array().expect("transparent array");
    assert_eq!(arr.len(), 2);
    assert_eq!(arr[0]["hash"], "aaa");
    assert_eq!(arr[0]["refs"][0], "main");
    assert_eq!(arr[1]["hash"], "bbb");
    assert!(arr[1]["parents"][0] == "aaa");
}

// ---------------------------------------------------------------------------
// Request DTO deserialisation (camelCase)
// ---------------------------------------------------------------------------

#[test]
fn git_stage_request_deserialises_camelcase() {
    let project_id = Uuid::from_u128(1).to_string();
    let raw = json!({
        "projectId": project_id,
        "path": "src/lib.rs"
    });
    let dto: GitStageRequestDto = serde_json::from_value(raw).unwrap();
    assert_eq!(dto.project_id, project_id);
    assert_eq!(dto.path, "src/lib.rs");
}

#[test]
fn git_commit_request_deserialises_camelcase() {
    let project_id = Uuid::from_u128(2).to_string();
    let raw = json!({
        "projectId": project_id,
        "message": "feat: initial commit"
    });
    let dto: GitCommitRequestDto = serde_json::from_value(raw).unwrap();
    assert_eq!(dto.project_id, project_id);
    assert_eq!(dto.message, "feat: initial commit");
}

#[test]
fn git_checkout_request_deserialises_camelcase() {
    let project_id = Uuid::from_u128(3).to_string();
    let raw = json!({
        "projectId": project_id,
        "branch": "feature/awesome"
    });
    let dto: GitCheckoutRequestDto = serde_json::from_value(raw).unwrap();
    assert_eq!(dto.project_id, project_id);
    assert_eq!(dto.branch, "feature/awesome");
}
