//
//  ProfileSpaceSettingsViewModel.swift
//  suixinkan
//

import Foundation

/// 个人空间配置接口协议，便于页面和测试注入真实或模拟 API。
@MainActor
protocol ProfileSpaceSettingsAPI {
    /// 拉取当前景区的个人空间配置。
    func profileInfo(scenicId: Int) async throws -> PhotographerProfileInfoResponse

    /// 更新当前景区的个人空间配置。
    func updateProfileInfo(_ request: UpdatePhotographerProfileInfoRequest) async throws

    /// 更新账号基础昵称或头像。
    func updateUserInfo(nickname: String?, password: String?, avatar: String?) async throws

    /// 添加个人日程。
    func addSchedule(_ request: AddPhotographerScheduleRequest) async throws

    /// 删除个人日程。
    func deleteSchedule(id: Int) async throws

    /// 上传裁剪后的摄影师头像。
    func uploadAvatar(data: Data, fileName: String) async throws
}

extension ProfileAPI: ProfileSpaceSettingsAPI {}

/// 个人空间配置校验错误。
enum ProfileSpaceSettingsValidationError: LocalizedError, Equatable {
    case emptyTime(String)
    case invalidTimeRange(String)
    case emptyLabel
    case duplicateLabel
    case labelTooLong
    case labelLimitReached
    case emptyDevice
    case emptyScheduleName
    case emptyScheduleTime
    case invalidScheduleTime

    var errorDescription: String? {
        switch self {
        case .emptyTime(let title):
            "请输入\(title)"
        case .invalidTimeRange(let title):
            "\(title)开始时间不能大于结束时间"
        case .emptyLabel:
            "标签不能为空"
        case .duplicateLabel:
            "标签已存在"
        case .labelTooLong:
            "标签最多20个字符"
        case .labelLimitReached:
            "最多8个标签"
        case .emptyDevice:
            "设备名称不能为空"
        case .emptyScheduleName:
            "请输入日程名称"
        case .emptyScheduleTime:
            "请选择日程时间"
        case .invalidScheduleTime:
            "结束时间必须晚于开始时间"
        }
    }
}

/// 个人空间配置 ViewModel，对齐 Android `ProfileInfoViewModel` 的状态和校验。
final class ProfileSpaceSettingsViewModel {

    private(set) var avatarURL = ""
    private(set) var realName = ""
    private(set) var nickname = ""
    private(set) var editingNickname = ""
    private(set) var introduction = ""
    private(set) var scenicCertification: [String] = []
    private(set) var attrLabels: [String] = []
    private(set) var shootLabels: [String] = []
    private(set) var businessStartTime: String?
    private(set) var businessEndTime: String?
    private(set) var holidayStartTime: String?
    private(set) var holidayEndTime: String?
    private(set) var cameraDevices: [String] = []
    private(set) var acceptOrderStatus = 0
    private(set) var scheduleMap: [String: [PhotographerSchedule]] = [:]
    private(set) var selectedDate: Date
    private(set) var isEditingProfile = false
    private(set) var isCertificationExpanded = false
    private(set) var isLoading = false
    private(set) var hasChanges = false

    var onStateChange: (() -> Void)?
    var onShowMessage: ((String) -> Void)?

    private var originalSnapshot = Snapshot()
    private let appStore: AppStore
    private let calendar: Calendar
    private let nowProvider: () -> Date

    init(
        appStore: AppStore = .shared,
        calendar: Calendar = Calendar(identifier: .gregorian),
        nowProvider: @escaping () -> Date = Date.init
    ) {
        self.appStore = appStore
        self.calendar = calendar
        self.nowProvider = nowProvider
        selectedDate = calendar.startOfDay(for: nowProvider())
    }

    /// 当前景区 ID。
    var scenicId: Int {
        appStore.session.currentScenicId
    }

    /// 选中日期对应日程。
    var selectedDateSchedules: [PhotographerSchedule] {
        scheduleMap[Self.dateKey(from: selectedDate, calendar: calendar)] ?? []
    }

    /// 从今天起连续 7 天。
    var weekDates: [Date] {
        let start = calendar.startOfDay(for: nowProvider())
        return (0..<7).compactMap { calendar.date(byAdding: .day, value: $0, to: start) }
    }

    /// 拉取个人空间配置。
    func load(api: any ProfileSpaceSettingsAPI) async {
        guard scenicId > 0 else {
            onShowMessage?("请先选择景区")
            return
        }
        setLoading(true)
        defer { setLoading(false) }
        do {
            let response = try await api.profileInfo(scenicId: scenicId)
            apply(response)
        } catch is CancellationError {
            return
        } catch {
            onShowMessage?(error.localizedDescription)
        }
    }

    /// 切换昵称和简介编辑状态，退出编辑时按 Android 逻辑单独保存昵称。
    func toggleProfileEditing(api: any ProfileSpaceSettingsAPI) async {
        if isEditingProfile {
            let newNickname = editingNickname.trimmingCharacters(in: .whitespacesAndNewlines)
            let currentNickname = nickname.trimmingCharacters(in: .whitespacesAndNewlines)
            guard !newNickname.isEmpty, newNickname != currentNickname else {
                isEditingProfile = false
                notifyStateChange()
                return
            }
            setLoading(true)
            defer { setLoading(false) }
            do {
                try await api.updateUserInfo(nickname: newNickname, password: nil, avatar: nil)
                nickname = newNickname
                appStore.session.userName = newNickname
                isEditingProfile = false
                onShowMessage?("修改成功")
                notifyStateChange()
            } catch is CancellationError {
                return
            } catch {
                onShowMessage?(error.localizedDescription)
            }
            return
        }
        editingNickname = nickname
        isEditingProfile = true
        notifyStateChange()
    }

    /// 更新头像 URL。
    func updateAvatarURL(_ url: String) {
        avatarURL = url
        appStore.session.avatar = url
        notifyStateChange()
    }

    /// 展开或收起景区认证标签。
    func toggleCertificationExpanded() {
        isCertificationExpanded.toggle()
        notifyStateChange()
    }

    /// 上传头像并重新拉取个人空间配置。
    func uploadAvatar(
        data: Data,
        fileName: String,
        api: any ProfileSpaceSettingsAPI
    ) async {
        setLoading(true)
        defer { setLoading(false) }
        do {
            try await api.uploadAvatar(data: data, fileName: fileName)
            onShowMessage?("上传成功")
            await load(api: api)
        } catch is CancellationError {
            return
        } catch {
            onShowMessage?(error.localizedDescription)
        }
    }

    /// 更新编辑昵称。
    func updateEditingNickname(_ value: String) {
        editingNickname = value
        notifyStateChange()
    }

    /// 更新简介。
    func updateIntroduction(_ value: String) {
        introduction = value
        updateHasChanges()
    }

    /// 添加个人标签。
    func addAttrLabel(_ value: String) {
        do {
            attrLabels = try adding(label: value, to: attrLabels)
            updateHasChanges()
        } catch {
            onShowMessage?(error.localizedDescription)
        }
    }

    /// 删除个人标签。
    func removeAttrLabel(_ label: String) {
        attrLabels.removeAll { $0 == label }
        updateHasChanges()
    }

    /// 添加拍摄说明标签。
    func addShootLabel(_ value: String) {
        do {
            shootLabels = try adding(label: value, to: shootLabels)
            updateHasChanges()
        } catch {
            onShowMessage?(error.localizedDescription)
        }
    }

    /// 删除拍摄说明标签。
    func removeShootLabel(_ label: String) {
        shootLabels.removeAll { $0 == label }
        updateHasChanges()
    }

    /// 更新工作日开始时间。
    func updateBusinessStartTime(_ value: String) {
        if let end = businessEndTime, !Self.isStart(value, before: end) {
            onShowMessage?("开始时间不能大于结束时间")
            return
        }
        businessStartTime = value
        updateHasChanges()
    }

    /// 更新工作日结束时间。
    func updateBusinessEndTime(_ value: String) {
        if let start = businessStartTime, !Self.isStart(start, before: value) {
            onShowMessage?("结束时间不能小于开始时间")
            return
        }
        businessEndTime = value
        updateHasChanges()
    }

    /// 更新节假日开始时间。
    func updateHolidayStartTime(_ value: String) {
        if let end = holidayEndTime, !Self.isStart(value, before: end) {
            onShowMessage?("开始时间不能大于结束时间")
            return
        }
        holidayStartTime = value
        updateHasChanges()
    }

    /// 更新节假日结束时间。
    func updateHolidayEndTime(_ value: String) {
        if let start = holidayStartTime, !Self.isStart(start, before: value) {
            onShowMessage?("结束时间不能小于开始时间")
            return
        }
        holidayEndTime = value
        updateHasChanges()
    }

    /// 添加相机设备。
    func addDevice(_ value: String) {
        let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty else {
            onShowMessage?(ProfileSpaceSettingsValidationError.emptyDevice.localizedDescription)
            return
        }
        cameraDevices.append(text)
        updateHasChanges()
    }

    /// 删除相机设备。
    func removeDevice(_ value: String) {
        cameraDevices.removeAll { $0 == value }
        updateHasChanges()
    }

    /// 更新接单状态。
    func updateAcceptOrderStatus(_ status: Int) {
        acceptOrderStatus = status
        updateHasChanges()
    }

    /// 选择日程日期。
    func selectDate(_ date: Date) {
        selectedDate = calendar.startOfDay(for: date)
        notifyStateChange()
    }

    /// 保存个人空间配置。
    func save(api: any ProfileSpaceSettingsAPI) async {
        do {
            let request = try makeSaveRequest()
            setLoading(true)
            defer { setLoading(false) }
            try await api.updateProfileInfo(request)
            originalSnapshot = currentSnapshot()
            hasChanges = false
            onShowMessage?("保存成功")
            notifyStateChange()
            await load(api: api)
        } catch is CancellationError {
            return
        } catch {
            onShowMessage?(error.localizedDescription)
        }
    }

    /// 构建个人空间保存请求。
    func makeSaveRequest() throws -> UpdatePhotographerProfileInfoRequest {
        guard let businessStartTime else { throw ProfileSpaceSettingsValidationError.emptyTime("工作日开始时间") }
        guard let businessEndTime else { throw ProfileSpaceSettingsValidationError.emptyTime("工作日结束时间") }
        guard let holidayStartTime else { throw ProfileSpaceSettingsValidationError.emptyTime("节假日开始时间") }
        guard let holidayEndTime else { throw ProfileSpaceSettingsValidationError.emptyTime("节假日结束时间") }
        guard Self.isStart(businessStartTime, before: businessEndTime) else {
            throw ProfileSpaceSettingsValidationError.invalidTimeRange("工作日")
        }
        guard Self.isStart(holidayStartTime, before: holidayEndTime) else {
            throw ProfileSpaceSettingsValidationError.invalidTimeRange("节假日")
        }
        return UpdatePhotographerProfileInfoRequest(
            scenicId: String(scenicId),
            description: introduction,
            cameraDevice: cameraDevices,
            attrLabel: attrLabels,
            shootLabel: shootLabels,
            businessStartTime: Self.fullTimeString(businessStartTime),
            businessEndTime: Self.fullTimeString(businessEndTime),
            holidayStartTime: Self.fullTimeString(holidayStartTime),
            holidayEndTime: Self.fullTimeString(holidayEndTime),
            businessRange: [],
            acceptOrderStatus: acceptOrderStatus
        )
    }

    /// 构建新增日程请求。
    func makeAddScheduleRequest(
        name: String,
        remark: String,
        startTime: String?,
        endTime: String?,
        scheduleDate: Date,
        orderNumber: String?
    ) throws -> AddPhotographerScheduleRequest {
        let scheduleName = name.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !scheduleName.isEmpty else { throw ProfileSpaceSettingsValidationError.emptyScheduleName }
        guard let startTime, let endTime else { throw ProfileSpaceSettingsValidationError.emptyScheduleTime }
        guard Self.isStart(startTime, before: endTime) else { throw ProfileSpaceSettingsValidationError.invalidScheduleTime }
        let order = orderNumber?.trimmingCharacters(in: .whitespacesAndNewlines)
        return AddPhotographerScheduleRequest(
            scenicId: String(scenicId),
            name: scheduleName,
            remark: remark,
            startTime: startTime,
            endTime: endTime,
            scheduleDate: Self.dateKey(from: scheduleDate, calendar: calendar),
            orderNumber: order?.isEmpty == true ? nil : order
        )
    }

    /// 添加日程并刷新个人空间配置。
    func addSchedule(
        name: String,
        remark: String,
        startTime: String?,
        endTime: String?,
        scheduleDate: Date,
        orderNumber: String?,
        api: any ProfileSpaceSettingsAPI
    ) async -> Bool {
        do {
            let request = try makeAddScheduleRequest(
                name: name,
                remark: remark,
                startTime: startTime,
                endTime: endTime,
                scheduleDate: scheduleDate,
                orderNumber: orderNumber
            )
            setLoading(true)
            defer { setLoading(false) }
            try await api.addSchedule(request)
            onShowMessage?("添加成功")
            await load(api: api)
            return true
        } catch is CancellationError {
            return false
        } catch {
            onShowMessage?(error.localizedDescription)
            return false
        }
    }

    /// 删除日程并刷新个人空间配置。
    func deleteSchedule(id: Int, api: any ProfileSpaceSettingsAPI) async {
        setLoading(true)
        defer { setLoading(false) }
        do {
            try await api.deleteSchedule(id: id)
            onShowMessage?("删除成功")
            await load(api: api)
        } catch is CancellationError {
            return
        } catch {
            onShowMessage?(error.localizedDescription)
        }
    }

    private func apply(_ response: PhotographerProfileInfoResponse) {
        realName = response.realName
        nickname = response.nickname
        editingNickname = response.nickname
        if !response.avatar.isEmpty {
            avatarURL = response.avatar
            appStore.session.avatar = response.avatar
        }
        scenicCertification = response.scenicCertification
        introduction = response.description
        attrLabels = response.attrLabel
        shootLabels = response.shootLabel
        cameraDevices = response.cameraDevice
        businessStartTime = Self.shortTimeString(response.businessStartTime)
        businessEndTime = Self.shortTimeString(response.businessEndTime)
        holidayStartTime = Self.shortTimeString(response.holidayStartTime)
        holidayEndTime = Self.shortTimeString(response.holidayEndTime)
        acceptOrderStatus = response.acceptOrderStatus
        scheduleMap = response.schedule
        appStore.session.userName = response.nickname
        appStore.session.realName = response.realName
        originalSnapshot = currentSnapshot()
        hasChanges = false
        notifyStateChange()
    }

    private func adding(label rawValue: String, to labels: [String]) throws -> [String] {
        let label = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !label.isEmpty else { throw ProfileSpaceSettingsValidationError.emptyLabel }
        guard label.count <= 20 else { throw ProfileSpaceSettingsValidationError.labelTooLong }
        guard labels.count < 8 else { throw ProfileSpaceSettingsValidationError.labelLimitReached }
        guard !labels.contains(label) else { throw ProfileSpaceSettingsValidationError.duplicateLabel }
        return labels + [label]
    }

    private func setLoading(_ loading: Bool) {
        isLoading = loading
        notifyStateChange()
    }

    private func updateHasChanges() {
        hasChanges = currentSnapshot() != originalSnapshot
        notifyStateChange()
    }

    private func notifyStateChange() {
        onStateChange?()
    }

    private func currentSnapshot() -> Snapshot {
        Snapshot(
            introduction: introduction,
            attrLabels: attrLabels,
            shootLabels: shootLabels,
            businessStartTime: businessStartTime,
            businessEndTime: businessEndTime,
            holidayStartTime: holidayStartTime,
            holidayEndTime: holidayEndTime,
            cameraDevices: cameraDevices,
            acceptOrderStatus: acceptOrderStatus
        )
    }

    private static func shortTimeString(_ value: String) -> String? {
        let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty, text != "00:00:00" else { return nil }
        if text.count >= 5 {
            return String(text.prefix(5))
        }
        return nil
    }

    static func fullTimeString(_ value: String) -> String {
        let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
        return text.count == 5 ? "\(text):00" : text
    }

    static func isStart(_ start: String, before end: String) -> Bool {
        minutes(from: start) < minutes(from: end)
    }

    static func minutes(from value: String) -> Int {
        let pieces = value.split(separator: ":").compactMap { Int($0) }
        guard pieces.count >= 2 else { return 0 }
        return pieces[0] * 60 + pieces[1]
    }

    static func dateKey(from date: Date, calendar: Calendar) -> String {
        let formatter = DateFormatter()
        formatter.calendar = calendar
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = calendar.timeZone
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.string(from: date)
    }

    /// 将日期展示为 Android 风格的 yyyy年M月d日。
    static func displayDate(_ date: Date, calendar: Calendar = Calendar(identifier: .gregorian)) -> String {
        let components = calendar.dateComponents([.year, .month, .day], from: date)
        return "\(components.year ?? 0)年\(components.month ?? 0)月\(components.day ?? 0)日"
    }

    /// 将日期展示为日数字。
    static func displayDay(_ date: Date, calendar: Calendar = Calendar(identifier: .gregorian)) -> String {
        String(calendar.component(.day, from: date))
    }

    /// 将日期展示为中文星期。
    static func displayWeekday(_ date: Date, calendar: Calendar = Calendar(identifier: .gregorian)) -> String {
        switch calendar.component(.weekday, from: date) {
        case 1: return "日"
        case 2: return "一"
        case 3: return "二"
        case 4: return "三"
        case 5: return "四"
        case 6: return "五"
        case 7: return "六"
        default: return ""
        }
    }
}

private struct Snapshot: Equatable {
    var introduction = ""
    var attrLabels: [String] = []
    var shootLabels: [String] = []
    var businessStartTime: String?
    var businessEndTime: String?
    var holidayStartTime: String?
    var holidayEndTime: String?
    var cameraDevices: [String] = []
    var acceptOrderStatus = 0
}
