import APIClient
import Foundation
import Testing
import WireProtocol
@testable import WebTerm

/// T-iOS-26 · ProjectDetailViewModel（详情 phase 状态机 + 400/404/500 显式
/// 错误路径，镜像 DiffViewModel 的四结局纪律）。
///
/// fetch 闭包注入（生产由 `forHost` 包 `APIClient.projectDetail(path:)`，
/// 其 builder/解码/状态码映射已在 APIClient 包内测过 —— 此处只测 VM 归约）。
@MainActor
@Suite("ProjectDetailViewModel")
struct ProjectDetailViewModelTests {
    private nonisolated static func detail(
        sessions: [ProjectSessionRef] = [],
        worktrees: [WorktreeInfo] = [],
        hasClaudeMd: Bool = false
    ) -> ProjectDetail {
        ProjectDetail(
            name: "web-terminal", path: "/repos/web-terminal", isGit: true,
            branch: "main", dirty: true, worktrees: worktrees,
            sessions: sessions, hasClaudeMd: hasClaudeMd, claudeMd: nil
        )
    }

    @Test("初始 .loading；load 成功 → .loaded（sessions/worktrees/hasClaudeMd 透传）")
    func loadSuccess() async throws {
        let payload = Self.detail(
            sessions: [ProjectSessionRef(
                id: UUID(), title: "web-terminal", status: .working,
                clientCount: 2, createdAt: 1, exited: false
            )],
            worktrees: [WorktreeInfo(
                path: "/repos/wt", branch: "feat/x", head: "abc",
                isMain: false, isCurrent: true, locked: nil, prunable: nil
            )],
            hasClaudeMd: true
        )
        let vm = ProjectDetailViewModel(path: payload.path, fetch: { payload })
        #expect(vm.phase == .loading)

        await vm.load()

        #expect(vm.phase == .loaded(payload))
    }

    @Test(
        "错误映射：400→pathInvalid、404→notFound、500/解码/传输→unavailable",
        arguments: [
            (APIClientError.projectPathInvalid, ProjectDetailViewModel.Failure.pathInvalid),
            (APIClientError.projectNotFound, .notFound),
            (APIClientError.projectDetailUnavailable, .unavailable),
            (APIClientError.invalidResponseBody, .unavailable),
        ]
    )
    func errorMapping(
        error: APIClientError, expected: ProjectDetailViewModel.Failure
    ) async {
        let vm = ProjectDetailViewModel(path: "/p", fetch: { throw error })

        await vm.load()

        #expect(vm.phase == .failed(expected))
    }

    @Test("传输层任意错误 → .unavailable（可重试兜底，绝不 crash）")
    func transportErrorFallsBackToUnavailable() async {
        let vm = ProjectDetailViewModel(
            path: "/p", fetch: { throw URLError(.notConnectedToInternet) }
        )

        await vm.load()

        #expect(vm.phase == .failed(.unavailable))
    }

    @Test("重试路径：failed 后再次 load 成功 → loaded")
    func retryAfterFailure() async {
        let flag = FailOnceFlag()
        let payload = Self.detail()
        let vm = ProjectDetailViewModel(path: payload.path, fetch: {
            if await flag.consumeShouldFail() { throw APIClientError.projectDetailUnavailable }
            return payload
        })

        await vm.load()
        #expect(vm.phase == .failed(.unavailable))

        await vm.load()
        #expect(vm.phase == .loaded(payload))
    }

    @Test("failureCopy：三种失败各有非空、互不相同的中文文案")
    func failureCopyIsDistinct() {
        let copies = [
            ProjectDetailScreen.failureCopy(.pathInvalid),
            ProjectDetailScreen.failureCopy(.notFound),
            ProjectDetailScreen.failureCopy(.unavailable),
        ]

        for copy in copies {
            #expect(!copy.title.isEmpty)
            #expect(!copy.detail.isEmpty)
        }
        #expect(Set(copies.map(\.title)).count == copies.count)
    }
}

/// @Sendable fetch 闭包里的可变一次性失败开关（actor 隔离）。
private actor FailOnceFlag {
    private var shouldFail = true

    func consumeShouldFail() -> Bool {
        defer { shouldFail = false }
        return shouldFail
    }
}
