//
//  ProfileSpaceSettingsViewModelTests.swift
//  suixinkanTests
//

import XCTest
@testable import suixinkan

@MainActor
/// 个人空间配置 ViewModel 与首页路由测试。
final class ProfileSpaceSettingsViewModelTests: XCTestCase {

    private var defaults: UserDefaults!
    private var appStore: AppStore!
    private var calendar: Calendar!
    private var fixedNow: Date!

    override func setUp() {
        super.setUp()
        defaults = UserDefaults(suiteName: "ProfileSpaceSettingsViewModelTests")!
        defaults.removePersistentDomain(forName: "ProfileSpaceSettingsViewModelTests")
        appStore = AppStore(defaults: defaults)
        appStore.session.configureTestAccount(scenicId: 11)
        AppStore.shared.logout()
        AppStore.shared.session.configureTestAccount(userId: "profile-space-shared", scenicId: 11)
        calendar = Calendar(identifier: .gregorian)
        calendar.timeZone = TimeZone(secondsFromGMT: 0)!
        fixedNow = calendar.date(from: DateComponents(year: 2026, month: 7, day: 8))!
    }

    override func tearDown() {
        AppStore.shared.logout()
        appStore.logout()
        defaults.removePersistentDomain(forName: "ProfileSpaceSettingsViewModelTests")
        super.tearDown()
    }

    func testProfileInfoDecodeSupportsEmptyStringsArraysAndScheduleMap() throws {
        let json = """
        {
          "id": "9",
          "scenic_id": "11",
          "photog_uid": "",
          "real_name": "",
          "nickname": "小林",
          "avatar": "",
          "scenic_certification": ["西湖", ""],
          "description": "",
          "attr_label": ["航拍", 8],
          "shoot_label": "",
          "camera_device": ["Sony A7"],
          "business_start_time": "09:00:00",
          "business_end_time": "18:00:00",
          "holiday_start_time": "",
          "holiday_end_time": "20:00:00",
          "business_range": ["1", 2],
          "accept_order_status": "1",
          "virtual_phone": "",
          "schedule": {
            "2026-07-08": [{
              "id": "22",
              "name": "旅拍",
              "remark": "",
              "start_time": "10:00",
              "end_time": "11:00",
              "schedule_date": "2026-07-08",
              "order_number": "NO1",
              "user_nickname": "游客",
              "user_phone": "13800000000",
              "order_status": "2",
              "order_status_name": "待拍摄"
            }]
          }
        }
        """.data(using: .utf8)!

        let response = try JSONDecoder().decode(PhotographerProfileInfoResponse.self, from: json)

        XCTAssertEqual(response.id, 9)
        XCTAssertEqual(response.photogUid, 0)
        XCTAssertEqual(response.attrLabel, ["航拍", "8"])
        XCTAssertEqual(response.shootLabel, [])
        XCTAssertEqual(response.businessRange, [1, 2])
        XCTAssertEqual(response.schedule["2026-07-08"]?.first?.id, 22)
        XCTAssertEqual(response.schedule["2026-07-08"]?.first?.userPhone, "13800000000")
    }

    func testProfileInfoEnvelopeDecodeSupportsEmptyScheduleArray() throws {
        let json = """
        {
          "code": 100000,
          "data": {
            "accept_order_status": 1,
            "attr_label": [],
            "avatar": "https://vipsky.oss-cn-shanghai.aliyuncs.com/avatar/20260707/128/E7036CC996884E8A963A3962A9E72850_avatar_1783416288.jpg",
            "business_end_time": "",
            "business_start_time": "",
            "camera_device": [],
            "description": "",
            "holiday_end_time": "",
            "holiday_start_time": "",
            "id": 732,
            "nickname": "陆久银12",
            "real_name": "陆久银",
            "scenic_certification": [
              "伊犁那拉提景区-5A"
            ],
            "schedule": [],
            "shoot_label": []
          },
          "msg": "success",
          "time": "2026-07-08 16:16:18"
        }
        """.data(using: .utf8)!

        let envelope = try JSONDecoder().decode(APIEnvelope<PhotographerProfileInfoResponse>.self, from: json)
        let response = try XCTUnwrap(envelope.data)

        XCTAssertEqual(response.id, 732)
        XCTAssertEqual(response.scenicId, 0)
        XCTAssertEqual(response.photogUid, 0)
        XCTAssertEqual(response.realName, "陆久银")
        XCTAssertEqual(response.nickname, "陆久银12")
        XCTAssertEqual(response.scenicCertification, ["伊犁那拉提景区-5A"])
        XCTAssertEqual(response.acceptOrderStatus, 1)
        XCTAssertEqual(response.schedule, [:])
        XCTAssertEqual(response.virtualPhone, "")
    }

    func testLabelValidationCoversEmptyTooLongDuplicateAndLimit() {
        let viewModel = makeViewModel()
        var messages: [String] = []
        viewModel.onShowMessage = { messages.append($0) }

        viewModel.addAttrLabel(" ")
        XCTAssertEqual(messages.last, "标签不能为空")

        viewModel.addAttrLabel(String(repeating: "a", count: 21))
        XCTAssertEqual(messages.last, "标签最多20个字符")

        viewModel.addAttrLabel("航拍")
        viewModel.addAttrLabel("航拍")
        XCTAssertEqual(viewModel.attrLabels, ["航拍"])
        XCTAssertEqual(messages.last, "标签已存在")

        for index in 1...7 {
            viewModel.addAttrLabel("标签\(index)")
        }
        viewModel.addAttrLabel("第九个")
        XCTAssertEqual(viewModel.attrLabels.count, 8)
        XCTAssertEqual(messages.last, "最多8个标签")
    }

    func testBusinessTimeValidationRejectsInvalidRanges() throws {
        let viewModel = makeViewModel()
        var messages: [String] = []
        viewModel.onShowMessage = { messages.append($0) }

        viewModel.updateBusinessEndTime("10:00")
        viewModel.updateBusinessStartTime("11:00")
        XCTAssertNil(viewModel.businessStartTime)
        XCTAssertEqual(messages.last, "开始时间不能大于结束时间")

        viewModel.updateHolidayStartTime("13:00")
        viewModel.updateHolidayEndTime("12:00")
        XCTAssertNil(viewModel.holidayEndTime)
        XCTAssertEqual(messages.last, "结束时间不能小于开始时间")

        viewModel.updateBusinessStartTime("09:00")
        viewModel.updateBusinessEndTime("18:00")
        viewModel.updateHolidayStartTime("08:00")
        viewModel.updateHolidayEndTime("20:00")

        viewModel.updateBusinessEndTime("08:00")
        XCTAssertEqual(viewModel.businessEndTime, "18:00")
        XCTAssertNoThrow(try viewModel.makeSaveRequest())
    }

    func testHasChangesAndSaveRequestFieldsMatchAndroid() async throws {
        let api = FakeProfileSpaceSettingsAPI(response: .loaded())
        let viewModel = makeViewModel()

        await viewModel.load(api: api)
        XCTAssertFalse(viewModel.hasChanges)

        viewModel.updateIntroduction("更新后的简介")
        viewModel.addDevice("Nikon Z8")
        viewModel.updateAcceptOrderStatus(2)
        XCTAssertTrue(viewModel.hasChanges)

        await viewModel.save(api: api)

        let request = try XCTUnwrap(api.lastUpdateRequest)
        XCTAssertEqual(request.scenicId, "11")
        XCTAssertEqual(request.description, "更新后的简介")
        XCTAssertEqual(request.cameraDevice, ["Sony A7", "Nikon Z8"])
        XCTAssertEqual(request.attrLabel, ["航拍"])
        XCTAssertEqual(request.shootLabel, ["亲子"])
        XCTAssertEqual(request.businessStartTime, "09:00:00")
        XCTAssertEqual(request.businessEndTime, "18:00:00")
        XCTAssertEqual(request.holidayStartTime, "10:00:00")
        XCTAssertEqual(request.holidayEndTime, "20:00:00")
        XCTAssertEqual(request.businessRange, [])
        XCTAssertEqual(request.acceptOrderStatus, 2)
        XCTAssertFalse(viewModel.hasChanges)
    }

    func testAddScheduleValidationAndDeleteId() async throws {
        let api = FakeProfileSpaceSettingsAPI(response: .loaded())
        let viewModel = makeViewModel()
        let scheduleDate = try XCTUnwrap(calendar.date(from: DateComponents(year: 2026, month: 7, day: 9)))

        XCTAssertThrowsError(try viewModel.makeAddScheduleRequest(
            name: "",
            remark: "内容",
            startTime: "10:00",
            endTime: "11:00",
            scheduleDate: scheduleDate,
            orderNumber: nil
        )) { error in
            XCTAssertEqual(error as? ProfileSpaceSettingsValidationError, .emptyScheduleName)
        }

        XCTAssertThrowsError(try viewModel.makeAddScheduleRequest(
            name: "日程",
            remark: "内容",
            startTime: "12:00",
            endTime: "11:00",
            scheduleDate: scheduleDate,
            orderNumber: nil
        )) { error in
            XCTAssertEqual(error as? ProfileSpaceSettingsValidationError, .invalidScheduleTime)
        }

        let didAdd = await viewModel.addSchedule(
            name: "  日程  ",
            remark: "内容",
            startTime: "10:00",
            endTime: "11:00",
            scheduleDate: scheduleDate,
            orderNumber: "  ",
            api: api
        )

        XCTAssertTrue(didAdd)
        XCTAssertEqual(api.lastAddScheduleRequest?.scenicId, "11")
        XCTAssertEqual(api.lastAddScheduleRequest?.name, "日程")
        XCTAssertEqual(api.lastAddScheduleRequest?.scheduleDate, "2026-07-09")
        XCTAssertNil(api.lastAddScheduleRequest?.orderNumber)

        await viewModel.deleteSchedule(id: 33, api: api)
        XCTAssertEqual(api.lastDeletedScheduleId, 33)
    }

    func testSpaceSettingsRoutePushesProfileSpaceSettingsPage() {
        AppStore.shared.session.currentScenicId = 11
        let root = UIViewController()
        let navigationController = UINavigationController(rootViewController: root)

        HomeRouteHandler.open(uri: "space_settings", from: root, storeItem: nil)

        XCTAssertTrue(navigationController.topViewController is ProfileSpaceSettingsViewController)
    }

    func testCertificationExpansionAndAvatarUploadRefresh() async {
        let response = PhotographerProfileInfoResponse.loaded(certifications: ["景区1", "景区2", "景区3", "景区4"])
        let api = FakeProfileSpaceSettingsAPI(response: response)
        let viewModel = makeViewModel()
        await viewModel.load(api: api)

        XCTAssertFalse(viewModel.isCertificationExpanded)
        viewModel.toggleCertificationExpanded()
        XCTAssertTrue(viewModel.isCertificationExpanded)

        let avatarData = Data([1, 2, 3])
        await viewModel.uploadAvatar(data: avatarData, fileName: "avatar.jpg", api: api)

        XCTAssertEqual(api.uploadedAvatarData, avatarData)
        XCTAssertEqual(api.uploadedAvatarFileName, "avatar.jpg")
        XCTAssertEqual(viewModel.avatarURL, "https://example.com/new-avatar.jpg")
    }

    func testSystemSettingsRoutePushesSettingPage() {
        AppStore.shared.session.currentScenicId = 11
        let tabBarController = MainTabBarController()
        tabBarController.loadViewIfNeeded()
        guard
            let navigationController = tabBarController.selectedViewController as? UINavigationController,
            let root = navigationController.viewControllers.first
        else {
            XCTFail("首页 Tab 未正确初始化")
            return
        }

        HomeRouteHandler.open(uri: "system_settings", from: root, storeItem: nil)

        XCTAssertEqual(tabBarController.selectedIndex, 0)
        XCTAssertTrue(navigationController.topViewController is SettingViewController)
    }

    private func makeViewModel() -> ProfileSpaceSettingsViewModel {
        ProfileSpaceSettingsViewModel(
            appStore: appStore,
            calendar: calendar,
            nowProvider: { self.fixedNow }
        )
    }
}

@MainActor
private final class FakeProfileSpaceSettingsAPI: ProfileSpaceSettingsAPI {
    var response: PhotographerProfileInfoResponse
    var lastUpdateRequest: UpdatePhotographerProfileInfoRequest?
    var lastAddScheduleRequest: AddPhotographerScheduleRequest?
    var lastDeletedScheduleId: Int?
    var uploadedAvatarData: Data?
    var uploadedAvatarFileName: String?

    init(response: PhotographerProfileInfoResponse) {
        self.response = response
    }

    func profileInfo(scenicId: Int) async throws -> PhotographerProfileInfoResponse {
        response
    }

    func updateProfileInfo(_ request: UpdatePhotographerProfileInfoRequest) async throws {
        lastUpdateRequest = request
        response = PhotographerProfileInfoResponse(
            id: response.id,
            scenicId: Int(request.scenicId) ?? 0,
            photogUid: response.photogUid,
            realName: response.realName,
            nickname: response.nickname,
            avatar: response.avatar,
            scenicCertification: response.scenicCertification,
            description: request.description,
            attrLabel: request.attrLabel,
            shootLabel: request.shootLabel,
            cameraDevice: request.cameraDevice,
            businessStartTime: request.businessStartTime,
            businessEndTime: request.businessEndTime,
            holidayStartTime: request.holidayStartTime,
            holidayEndTime: request.holidayEndTime,
            businessRange: request.businessRange,
            acceptOrderStatus: request.acceptOrderStatus,
            schedule: response.schedule,
            virtualPhone: response.virtualPhone
        )
    }

    func updateUserInfo(nickname: String?, password: String?, avatar: String?) async throws {}

    func addSchedule(_ request: AddPhotographerScheduleRequest) async throws {
        lastAddScheduleRequest = request
    }

    func deleteSchedule(id: Int) async throws {
        lastDeletedScheduleId = id
    }

    func uploadAvatar(data: Data, fileName: String) async throws {
        uploadedAvatarData = data
        uploadedAvatarFileName = fileName
        response = PhotographerProfileInfoResponse(
            id: response.id,
            scenicId: response.scenicId,
            photogUid: response.photogUid,
            realName: response.realName,
            nickname: response.nickname,
            avatar: "https://example.com/new-avatar.jpg",
            scenicCertification: response.scenicCertification,
            description: response.description,
            attrLabel: response.attrLabel,
            shootLabel: response.shootLabel,
            cameraDevice: response.cameraDevice,
            businessStartTime: response.businessStartTime,
            businessEndTime: response.businessEndTime,
            holidayStartTime: response.holidayStartTime,
            holidayEndTime: response.holidayEndTime,
            businessRange: response.businessRange,
            acceptOrderStatus: response.acceptOrderStatus,
            schedule: response.schedule,
            virtualPhone: response.virtualPhone
        )
    }
}

private extension PhotographerProfileInfoResponse {
    static func loaded() -> PhotographerProfileInfoResponse {
        loaded(certifications: ["西湖"])
    }

    static func loaded(certifications: [String]) -> PhotographerProfileInfoResponse {
        PhotographerProfileInfoResponse(
            id: 1,
            scenicId: 11,
            photogUid: 100,
            realName: "林摄影",
            nickname: "小林",
            avatar: "https://example.com/a.jpg",
            scenicCertification: certifications,
            description: "简介",
            attrLabel: ["航拍"],
            shootLabel: ["亲子"],
            cameraDevice: ["Sony A7"],
            businessStartTime: "09:00:00",
            businessEndTime: "18:00:00",
            holidayStartTime: "10:00:00",
            holidayEndTime: "20:00:00",
            businessRange: [],
            acceptOrderStatus: 1,
            schedule: [
                "2026-07-08": [
                    PhotographerSchedule(
                        id: 22,
                        name: "旅拍",
                        remark: "西湖",
                        startTime: "10:00",
                        endTime: "11:00",
                        scheduleDate: "2026-07-08",
                        userNickname: "游客",
                        userPhone: "13800000000"
                    ),
                ],
            ],
            virtualPhone: ""
        )
    }
}
