//
//  ScenicApplicationViewModel.swift
//  suixinkan
//
//  Created by Codex on 2026/6/23.
//

import Foundation
import Observation

/// 景区申请本地图片草稿实体，表示尚未或正在上传的图片。
struct ScenicApplicationImageDraft: Equatable, Identifiable {
    let id: UUID
    let data: Data
    let fileName: String
    var uploadedURL: String?
    var uploading: Bool
    var progress: Int

    /// 创建景区申请图片草稿。
    init(id: UUID = UUID(), data: Data, fileName: String, uploadedURL: String? = nil, uploading: Bool = false, progress: Int = 0) {
        self.id = id
        self.data = data
        self.fileName = fileName
        self.uploadedURL = uploadedURL
        self.uploading = uploading
        self.progress = progress
    }
}

@MainActor
@Observable
/// 景区申请 ViewModel，负责新增景区申请表单、图片上传和待审核记录回填。
final class ScenicApplicationViewModel {
    var scenicName = ""
    var imageURLs = ""
    var localImages: [ScenicApplicationImageDraft] = []
    var selectedProvince = ""
    var selectedCity = ""
    var coopType = 1
    var remark = ""
    var agreed = false
    var submitting = false
    var message: String?
    var provinces: [ScenicAreaNode] = []
    var cities: [ScenicAreaNode] = []
    var pending: ScenicApplicationPendingResponse?
    var loading = false
    var loadFailed = false
    var loadFailureReason: String?
    var pendingLoadFailed = false
    var pendingLoadFailureReason: String?

    /// 返回当前申请是否只读；审核中的申请不可再次编辑提交。
    var isReadOnly: Bool {
        pending?.status == 1
    }

    /// 返回当前表单是否允许提交。
    var canSubmit: Bool {
        !isReadOnly && !submitting && validationMessage == nil
    }

    /// 返回提交按钮是否需要锁定。
    var isSubmitLocked: Bool {
        isReadOnly || submitting
    }

    /// 返回远程图片和本地图片总数。
    var uploadPlaceholderCount: Int {
        remoteImageURLs.count + localImages.count
    }

    /// 返回按行或逗号分割后的远程图片 URL。
    var remoteImageURLs: [String] {
        imageURLs
            .split(whereSeparator: \.isNewline)
            .flatMap { $0.split(separator: ",") }
            .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
            .filter { !$0.isEmpty }
    }

    /// 加载地区树和待审核景区申请。
    func loadInitial(api: any ScenicPermissionServing) async {
        loading = true
        loadFailed = false
        loadFailureReason = nil
        defer { loading = false }
        do {
            provinces = try await api.areas()
        } catch {
            let reason = "地区数据加载失败：\(error.localizedDescription)"
            message = reason
            loadFailed = true
            loadFailureReason = reason
            provinces = []
            cities = []
            clearPendingApplication()
            return
        }
        await reloadPending(api: api)
    }

    /// 重新加载待审核、审核中或驳回的景区申请。
    func reloadPending(api: any ScenicPermissionServing) async {
        pendingLoadFailed = false
        pendingLoadFailureReason = nil
        do {
            let pendingList = try await api.scenicApplicationPendingAll().items
            if let first = pendingList.first(where: { $0.status == 1 || $0.status == 3 || $0.status == 9 }) {
                applyPending(first)
            } else {
                clearPendingApplication()
            }
        } catch {
            clearPendingApplication()
            pendingLoadFailed = true
            pendingLoadFailureReason = error.localizedDescription
        }
    }

    /// 省份变更后刷新城市列表，并在旧城市不匹配时清空城市。
    func onProvinceChange() {
        let oldCity = selectedCity
        cities = provinces.first(where: { $0.name == selectedProvince })?.children ?? []
        if !cities.contains(where: { $0.name == oldCity }) {
            selectedCity = ""
        }
    }

    /// 添加一张本地景区图片。
    func addLocalImage(data: Data, fileName: String) {
        guard uploadPlaceholderCount < 20 else {
            message = "最多上传20张景区图片"
            return
        }
        localImages.append(ScenicApplicationImageDraft(data: data, fileName: fileName))
    }

    /// 删除一张远程图片。
    func removeRemoteImage(_ url: String) {
        imageURLs = remoteImageURLs.filter { $0 != url }.joined(separator: "\n")
    }

    /// 删除一张本地图片。
    func removeLocalImage(id: UUID) {
        localImages.removeAll { $0.id == id }
    }

    /// 提交景区申请；本地图片会先上传 OSS，全部成功后再提交业务表单。
    func submit(api: any ScenicPermissionServing, uploader: any OSSUploadServing) async {
        if let validationMessage {
            message = validationMessage
            return
        }
        guard !isSubmitLocked else { return }
        submitting = true
        defer { submitting = false }
        do {
            let uploaded = try await uploadLocalImagesIfNeeded(uploader: uploader)
            let urls = remoteImageURLs + uploaded
            if !urls.isEmpty {
                let remotePlaceholders = remoteImageURLs.map { makeUploadPlaceholder(from: $0, fileSize: 0) }
                let localPlaceholders = localImages.compactMap { item -> ScenicApplicationUploadPlaceholder? in
                    guard let uploadedURL = item.uploadedURL else { return nil }
                    return makeUploadPlaceholder(from: uploadedURL, fileName: item.fileName, fileSize: Int64(item.data.count))
                }
                try await api.scenicApplicationUploadPlaceholder(remotePlaceholders + localPlaceholders)
            }
            try await api.scenicSubmit(
                ScenicApplicationSubmitRequest(
                    scenicName: trimmedNilIfEmpty(scenicName),
                    scenicImages: urls.isEmpty ? nil : urls,
                    scenicProvince: selectedProvince,
                    scenicCity: selectedCity,
                    coopType: coopType,
                    remark: remark,
                    scenicId: pending?.scenicId ?? 0
                )
            )
            await reloadPending(api: api)
            message = "提交成功"
        } catch {
            message = error.localizedDescription
        }
    }

    /// 返回当前表单校验错误文案。
    var validationMessage: String? {
        if scenicName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            return "请输入景区名称"
        }
        if uploadPlaceholderCount == 0 {
            return "请至少上传一张景区图片"
        }
        if selectedProvince.isEmpty {
            return "请选择省份"
        }
        if selectedCity.isEmpty {
            return "请选择城市"
        }
        if !agreed {
            return "请先阅读并同意用户须知与隐私政策"
        }
        return nil
    }

    /// 上传尚未上传的本地图片，并返回最终 URL 列表。
    private func uploadLocalImagesIfNeeded(uploader: any OSSUploadServing) async throws -> [String] {
        var urls: [String] = []
        for index in localImages.indices {
            if let uploadedURL = localImages[index].uploadedURL {
                urls.append(uploadedURL)
                continue
            }
            localImages[index].uploading = true
            localImages[index].progress = max(localImages[index].progress, 1)
            do {
                let uploadedURL = try await uploader.uploadScenicApplicationImage(
                    data: localImages[index].data,
                    fileName: localImages[index].fileName,
                    scenicId: pending?.scenicId ?? 0
                ) { [weak self] progress in
                    Task { @MainActor in
                        guard let self, self.localImages.indices.contains(index) else { return }
                        self.localImages[index].progress = progress
                    }
                }
                localImages[index].uploadedURL = uploadedURL
                localImages[index].uploading = false
                localImages[index].progress = 100
                urls.append(uploadedURL)
            } catch {
                localImages[index].uploading = false
                localImages[index].progress = 0
                throw error
            }
        }
        return urls
    }

    /// 使用待审核记录回填表单。
    private func applyPending(_ item: ScenicApplicationPendingResponse) {
        pending = item
        scenicName = item.scenicName
        imageURLs = item.scenicImages
            .split(separator: ",")
            .map(String.init)
            .joined(separator: "\n")
        selectedProvince = item.scenicProvince
        selectedCity = item.scenicCity
        coopType = item.coopType
        remark = item.remark ?? ""
        agreed = true
        localImages = []
        onProvinceChange()
    }

    /// 清空待审核记录和表单内容。
    private func clearPendingApplication() {
        pending = nil
        scenicName = ""
        imageURLs = ""
        localImages = []
        selectedProvince = ""
        selectedCity = ""
        cities = []
        coopType = 1
        remark = ""
        agreed = false
    }

    /// 创建上传占位请求实体。
    private func makeUploadPlaceholder(from url: String, fileName: String? = nil, fileSize: Int64) -> ScenicApplicationUploadPlaceholder {
        ScenicApplicationUploadPlaceholder(
            fileName: fileName ?? ScenicUploadPlaceholderPolicy.fileName(from: url, fallbackPrefix: "scenic_upload"),
            fileType: ScenicUploadPlaceholderPolicy.fileType(from: url),
            fileSize: fileSize
        )
    }

    /// 返回去空白后的非空字符串，空值返回 nil。
    private func trimmedNilIfEmpty(_ value: String) -> String? {
        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
        return trimmed.isEmpty ? nil : trimmed
    }
}

/// 景区申请上传占位策略，负责从 URL 或文件名提取占位元信息。
enum ScenicUploadPlaceholderPolicy {
    /// 从原始 URL 或路径中提取文件名，缺失时使用稳定前缀兜底。
    static func fileName(from rawValue: String, fallbackPrefix: String, uuid: UUID = UUID()) -> String {
        let trimmed = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
        let stripped = trimmed
            .split(separator: "#", maxSplits: 1, omittingEmptySubsequences: false).first
            .map(String.init)?
            .split(separator: "?", maxSplits: 1, omittingEmptySubsequences: false).first
            .map(String.init) ?? trimmed
        let urlPathName = URL(string: trimmed)?.lastPathComponent
        let pathName = (stripped as NSString).lastPathComponent
        let decodedName = (urlPathName?.isEmpty == false ? urlPathName : pathName)?.removingPercentEncoding
        let fileName = decodedName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        guard !fileName.isEmpty, fileName != "/" else {
            return "\(fallbackPrefix)_\(uuid.uuidString.prefix(8))"
        }
        return fileName
    }

    /// 根据文件扩展名推断上传占位文件类型。
    static func fileType(from rawValue: String) -> String {
        let fileName = fileName(from: rawValue, fallbackPrefix: "upload")
        let ext = URL(fileURLWithPath: fileName).pathExtension.lowercased()
        if ["mp4", "mov", "mkv", "avi"].contains(ext) { return "video" }
        if ["pdf", "doc", "docx", "xls", "xlsx"].contains(ext) { return "file" }
        return "image"
    }
}
