//
//  CloudDriveViewModelTests.swift
//  suixinkanTests
//

import XCTest
@testable import suixinkan

@MainActor
/// 相册云盘 ViewModel 测试。
final class CloudDriveViewModelTests: XCTestCase {
    func testDisplayModeToggleSwitchesBetweenGridAndListAndNotifiesUI() {
        let viewModel = CloudDriveListViewModel()
        var notificationCount = 0
        viewModel.onStateChange = { notificationCount += 1 }

        viewModel.toggleDisplayMode()
        XCTAssertEqual(viewModel.displayMode, .list)

        viewModel.toggleDisplayMode()
        XCTAssertEqual(viewModel.displayMode, .grid)
        XCTAssertEqual(notificationCount, 2)
    }

    func testListFileDescriptionMatchesAndroidDateSizeAndFolderCount() {
        let image = CloudFile(
            id: 1,
            name: "海边.jpg",
            createdAt: "2026-07-10 18:32:15",
            type: 2,
            fileSize: 2_048
        )
        let folder = CloudFile(
            id: 2,
            name: "旅行",
            createdAt: "2026-07-09 09:08:07",
            childNum: 3,
            type: 99
        )

        XCTAssertEqual(CloudDriveFormatter.fileDescription(image), "2026-07-10 | 2.00 KB")
        XCTAssertEqual(CloudDriveFormatter.fileDescription(folder), "2026-07-09 | 3个项目")
        XCTAssertEqual(CloudDriveFormatter.fileSize(0), "0 B")
    }

    func testLoadMoreSearchFilterSortAndFolderNavigation() async throws {
        let api = MockCloudDriveAPI()
        api.listResponses = [
            CloudFileListResponse(total: 3, list: [file(id: 1), folder(id: 2)]),
            CloudFileListResponse(total: 3, list: [file(id: 3)]),
            CloudFileListResponse(total: 1, list: [file(id: 4)]),
            CloudFileListResponse(total: 1, list: [file(id: 5)]),
            CloudFileListResponse(total: 0, list: []),
        ]
        let viewModel = CloudDriveListViewModel()

        await viewModel.refresh(api: api)
        await viewModel.loadMoreIfNeeded(lastVisibleIndex: 1, api: api)
        viewModel.updateSearchText("图")
        await viewModel.chooseFilterType(.image, api: api)
        await viewModel.chooseSortType(.createdAscending, api: api)
        _ = await viewModel.handleFileTap(folder(id: 9), api: api)

        XCTAssertEqual(viewModel.files.map(\.id), [])
        XCTAssertEqual(viewModel.pathStack.map(\.id), [0, 9])
        XCTAssertEqual(api.listCalls.map(\.page), [1, 2, 1, 1, 1])
        XCTAssertEqual(api.listCalls[2].name, "图")
        XCTAssertEqual(api.listCalls[2].type, 2)
        XCTAssertEqual(api.listCalls[3].orderBy, 1)
        XCTAssertEqual(api.listCalls[4].name, "")
    }

    func testBackRestoresCachedRootAndGoHome() async {
        let api = MockCloudDriveAPI()
        api.listResponses = [
            CloudFileListResponse(total: 1, list: [folder(id: 2)]),
            CloudFileListResponse(total: 1, list: [file(id: 5)]),
        ]
        let viewModel = CloudDriveListViewModel()
        await viewModel.refresh(api: api)
        _ = await viewModel.handleFileTap(folder(id: 2), api: api)

        let handled = await viewModel.navigateBack(api: api)

        XCTAssertTrue(handled)
        XCTAssertEqual(viewModel.pathStack.map(\.id), [0])
        XCTAssertEqual(viewModel.files.map(\.id), [2])
        XCTAssertEqual(api.listCalls.count, 2)
    }

    func testRootBackReturnsFalseWithoutRequest() async {
        let api = MockCloudDriveAPI()
        let viewModel = CloudDriveListViewModel()

        let handled = await viewModel.navigateBack(api: api)

        XCTAssertFalse(handled)
        XCTAssertEqual(viewModel.pathStack.map(\.id), [0])
        XCTAssertTrue(api.listCalls.isEmpty)
    }

    func testBreadcrumbAndGoHomeRestoreFolderCaches() async {
        let api = MockCloudDriveAPI()
        api.listResponses = [
            CloudFileListResponse(total: 1, list: [folder(id: 2)]),
            CloudFileListResponse(total: 1, list: [folder(id: 3)]),
            CloudFileListResponse(total: 1, list: [file(id: 9)]),
        ]
        let viewModel = CloudDriveListViewModel()

        await viewModel.refresh(api: api)
        _ = await viewModel.handleFileTap(folder(id: 2), api: api)
        _ = await viewModel.handleFileTap(folder(id: 3), api: api)
        await viewModel.navigateToPathIndex(1, api: api)

        XCTAssertEqual(viewModel.pathStack.map(\.id), [0, 2])
        XCTAssertEqual(viewModel.files.map(\.id), [3])
        XCTAssertEqual(api.listCalls.count, 3)

        await viewModel.goHome(api: api)

        XCTAssertEqual(viewModel.pathStack.map(\.id), [0])
        XCTAssertEqual(viewModel.files.map(\.id), [2])
        XCTAssertEqual(api.listCalls.count, 3)
    }

    func testFolderDepthLimitPreventsSixthLevel() async {
        let api = MockCloudDriveAPI()
        let viewModel = CloudDriveListViewModel()
        var message: String?
        viewModel.onShowMessage = { message = $0 }

        for id in 1 ... 4 {
            _ = await viewModel.handleFileTap(folder(id: id), api: api)
        }
        await viewModel.addFolder(name: "第六层", api: api)

        XCTAssertEqual(viewModel.pathStack.count, 5)
        XCTAssertEqual(message, "不能创建更深的文件夹")
        XCTAssertTrue(api.createFolderCalls.isEmpty)
    }

    func testCreateDeleteMoveAndRenameRefresh() async {
        let api = MockCloudDriveAPI()
        api.listResponses = Array(repeating: CloudFileListResponse(), count: 5)
        let viewModel = CloudDriveListViewModel()

        await viewModel.addFolder(name: " 新目录 ", api: api)
        viewModel.setControlFile(file(id: 7, type: 2))
        await viewModel.deleteCurrentFile(api: api)
        await viewModel.moveCurrentFile(targetFolderId: 9, api: api)
        await viewModel.saveCurrentFileName(" 新名.jpg ", api: api)
        viewModel.setControlFile(folder(id: 8))
        await viewModel.saveCurrentFileName(" 新目录名 ", api: api)

        XCTAssertEqual(api.createFolderCalls.first?.name, "新目录")
        XCTAssertEqual(api.deleteCalls.first?.list.first?.id, 7)
        XCTAssertEqual(api.moveCalls.first?.targetFolderId, 9)
        XCTAssertEqual(api.renameFileCalls.first?.fileName, "新名.jpg")
        XCTAssertEqual(api.renameFolderCalls.first?.name, "新目录名")
    }

    func testUploadPermissionFailureShowsReason() async {
        let api = MockCloudDriveAPI()
        api.permission = CloudUploadPermissionResponse(canUpload: false, reason: "空间不足")
        let viewModel = CloudDriveListViewModel()
        var message: String?
        viewModel.onShowMessage = { message = $0 }

        let canUpload = await viewModel.checkCanUpload(api: api)

        XCTAssertFalse(canUpload)
        XCTAssertEqual(message, "空间不足")
    }

    func testEmptyFolderAndRenameNamesAreRejected() async {
        let api = MockCloudDriveAPI()
        let viewModel = CloudDriveListViewModel()
        var messages: [String] = []
        viewModel.onShowMessage = { messages.append($0) }

        await viewModel.addFolder(name: "   ", api: api)
        viewModel.setControlFile(file(id: 7))
        await viewModel.saveCurrentFileName("\n", api: api)

        XCTAssertEqual(messages, ["名称不能为空", "名称不能为空"])
        XCTAssertTrue(api.createFolderCalls.isEmpty)
        XCTAssertTrue(api.renameFileCalls.isEmpty)
    }

    func testUploadCompletionRefreshesOnlyCurrentFolder() async {
        let api = MockCloudDriveAPI()
        api.listResponses = [
            CloudFileListResponse(total: 1, list: [file(id: 1)]),
            CloudFileListResponse(total: 1, list: [file(id: 2)]),
        ]
        let viewModel = CloudDriveListViewModel()

        await viewModel.refresh(api: api)
        await viewModel.handleUploadCompleted(parentFolderId: 9, api: api)
        XCTAssertEqual(api.listCalls.count, 1)

        await viewModel.handleUploadCompleted(parentFolderId: 0, api: api)
        XCTAssertEqual(api.listCalls.count, 2)
        XCTAssertEqual(viewModel.files.map(\.id), [2])
    }

    private func file(id: Int, type: Int = 2) -> CloudFile {
        CloudFile(id: id, fileUrl: "https://cdn/\(id).jpg", name: "文件\(id)", type: type)
    }

    private func folder(id: Int) -> CloudFile {
        CloudFile(id: id, name: "目录\(id)", type: 99)
    }
}

@MainActor
private final class MockCloudDriveAPI: CloudDriveServing {
    var listResponses: [CloudFileListResponse] = []
    var permission = CloudUploadPermissionResponse(canUpload: true, reason: "")
    var listCalls: [(parentFolderId: Int, name: String, type: Int, orderBy: Int, page: Int, pageSize: Int)] = []
    var createFolderCalls: [(parentFolderId: Int, name: String)] = []
    var uploadCalls: [(parentFolderId: Int, fileUrl: String, fileName: String)] = []
    var deleteCalls: [CloudFileDeleteRequest] = []
    var moveCalls: [CloudFileMoveRequest] = []
    var renameFolderCalls: [(fileId: Int, name: String)] = []
    var renameFileCalls: [(fileId: Int, fileName: String)] = []

    func cloudFileList(parentFolderId: Int, name: String, type: Int, orderBy: Int, page: Int, pageSize: Int) async throws -> CloudFileListResponse {
        listCalls.append((parentFolderId, name, type, orderBy, page, pageSize))
        return listResponses.isEmpty ? CloudFileListResponse() : listResponses.removeFirst()
    }

    func createCloudFolder(parentFolderId: Int, name: String) async throws {
        createFolderCalls.append((parentFolderId, name))
    }

    func uploadCloudFile(parentFolderId: Int, fileUrl: String, fileName: String) async throws {
        uploadCalls.append((parentFolderId, fileUrl, fileName))
    }

    func deleteCloudFiles(_ request: CloudFileDeleteRequest) async throws {
        deleteCalls.append(request)
    }

    func moveCloudFiles(_ request: CloudFileMoveRequest) async throws {
        moveCalls.append(request)
    }

    func checkUploadPermission() async throws -> CloudUploadPermissionResponse {
        permission
    }

    func modifyCloudFolderName(fileId: Int, name: String) async throws {
        renameFolderCalls.append((fileId, name))
    }

    func modifyCloudFileName(fileId: Int, fileName: String) async throws {
        renameFileCalls.append((fileId, fileName))
    }
}
