//
//  TravelAlbumModels.swift
//  suixinkan
//
//  Created by Codex on 2026/6/29.
//

import Foundation

/// 旅拍相册用户摘要。
struct TravelAlbumUser: Decodable, Equatable, Hashable {
    let id: Int
    let phone: String

    enum CodingKeys: String, CodingKey {
        case id, phone
    }

    init(id: Int = 0, phone: String = "") {
        self.id = id
        self.phone = phone
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = container.lossyInt(forKey: .id)
        phone = container.lossyString(forKey: .phone)
    }
}

/// 旅拍相册任务实体。
struct TravelAlbumItem: Decodable, Identifiable, Equatable, Hashable {
    let id: Int
    let storeUserId: Int
    let name: String
    /// 1 先拍后买，2 先买后拍
    let type: Int
    let orderNumber: String
    let materialNum: Int
    let materialPrice: Int
    let materialPackagePrice: Int
    let photoPrice: Int
    let coverURL: String
    let userId: Int
    let status: Int
    let createdAt: String
    let updatedAt: String
    let user: TravelAlbumUser?

    enum CodingKeys: String, CodingKey {
        case id
        case storeUserId = "store_user_id"
        case name, type
        case orderNumber = "order_number"
        case materialNum = "material_num"
        case materialPrice = "material_price"
        case materialPackagePrice = "material_package_price"
        case photoPrice = "photo_price"
        case coverURL = "cover_url"
        case userId = "user_id"
        case status
        case createdAt = "created_at"
        case updatedAt = "updated_at"
        case user
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = container.lossyInt(forKey: .id)
        storeUserId = container.lossyInt(forKey: .storeUserId)
        name = container.lossyString(forKey: .name)
        type = container.lossyInt(forKey: .type)
        orderNumber = container.lossyString(forKey: .orderNumber)
        materialNum = container.lossyInt(forKey: .materialNum)
        materialPrice = container.lossyInt(forKey: .materialPrice)
        materialPackagePrice = container.lossyInt(forKey: .materialPackagePrice)
        photoPrice = container.lossyInt(forKey: .photoPrice)
        coverURL = container.lossyString(forKey: .coverURL)
        userId = container.lossyInt(forKey: .userId)
        status = container.lossyInt(forKey: .status)
        createdAt = container.lossyString(forKey: .createdAt)
        updatedAt = container.lossyString(forKey: .updatedAt)
        user = try? container.decodeIfPresent(TravelAlbumUser.self, forKey: .user)
    }

    var displayPhone: String {
        user?.phone ?? ""
    }
}

/// 可绑定订单实体（买了再拍）。
struct TravelAlbumAvailableOrder: Decodable, Identifiable, Equatable, Hashable {
    let projectName: String
    let orderNumber: String
    let userPhone: String
    let userId: Int

    var id: String { orderNumber }

    enum CodingKeys: String, CodingKey {
        case projectName = "project_name"
        case orderNumber = "order_number"
        case userPhone = "user_phone"
        case userId = "user_id"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        projectName = container.lossyString(forKey: .projectName)
        orderNumber = container.lossyString(forKey: .orderNumber)
        userPhone = container.lossyString(forKey: .userPhone)
        userId = container.lossyInt(forKey: .userId)
    }
}

/// 旅拍相册素材实体。
struct TravelAlbumMaterial: Decodable, Identifiable, Equatable {
    let id: Int
    let userEquityTravelId: Int
    let status: Int
    let orderNumber: String
    let userId: Int
    let fileName: String
    let fileType: Int
    let fileURL: String
    let fileSize: Int
    let coverURL: String
    let isPurchased: Bool
    let createdAt: String
    let updatedAt: String

    enum CodingKeys: String, CodingKey {
        case id
        case userEquityTravelId = "user_equity_travel_id"
        case status
        case orderNumber = "order_number"
        case userId = "user_id"
        case fileName = "file_name"
        case fileType = "file_type"
        case fileURL = "file_url"
        case fileSize = "file_size"
        case coverURL = "cover_url"
        case isPurchased = "is_purchased"
        case createdAt = "created_at"
        case updatedAt = "updated_at"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = container.lossyInt(forKey: .id)
        userEquityTravelId = container.lossyInt(forKey: .userEquityTravelId)
        status = container.lossyInt(forKey: .status)
        orderNumber = container.lossyString(forKey: .orderNumber)
        userId = container.lossyInt(forKey: .userId)
        fileName = container.lossyString(forKey: .fileName)
        fileType = container.lossyInt(forKey: .fileType)
        fileURL = container.lossyString(forKey: .fileURL)
        fileSize = container.lossyInt(forKey: .fileSize)
        coverURL = container.lossyString(forKey: .coverURL)
        isPurchased = container.lossyBool(forKey: .isPurchased)
        createdAt = container.lossyString(forKey: .createdAt)
        updatedAt = container.lossyString(forKey: .updatedAt)
    }

    /// 预览用大图 URL，优先原图地址。
    var previewURLString: String {
        if !fileURL.isEmpty { return fileURL }
        return coverURL
    }

    /// 列表缩略图 URL，优先封面。
    var thumbnailURLString: String {
        if !coverURL.isEmpty { return coverURL }
        return fileURL
    }
}

/// 创建旅拍相册请求。
struct TravelAlbumCreateRequest: Encodable {
    let name: String
    let type: Int
    let orderNumber: String?
    let materialNum: Int?
    let materialPrice: Double?
    let materialPackagePrice: Double?
    let photoPrice: Double?

    enum CodingKeys: String, CodingKey {
        case name, type
        case orderNumber = "order_number"
        case materialNum = "material_num"
        case materialPrice = "material_price"
        case materialPackagePrice = "material_package_price"
        case photoPrice = "photo_price"
    }
}

/// 创建旅拍相册响应。
struct TravelAlbumCreateResponse: Decodable {
    let id: Int

    init(id: Int) {
        self.id = id
    }

    enum CodingKeys: String, CodingKey {
        case id
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = container.lossyInt(forKey: .id)
    }
}

/// 上传素材登记请求。
struct TravelAlbumUploadMaterialRequest: Encodable {
    let userEquityTravelId: Int
    let fileName: String
    let fileURL: String

    enum CodingKeys: String, CodingKey {
        case userEquityTravelId = "user_equity_travel_id"
        case fileName = "file_name"
        case fileURL = "file_url"
    }
}

/// 删除旅拍相册请求。
struct TravelAlbumDeleteRequest: Encodable {
    let id: Int
}

/// 编辑旅拍相册请求。
struct TravelAlbumEditRequest: Encodable {
    let id: Int
    let name: String?
    let materialPackagePrice: Double?

    enum CodingKeys: String, CodingKey {
        case id, name
        case materialPackagePrice = "material_package_price"
    }
}

/// 删除素材请求。
struct TravelAlbumDeleteMaterialRequest: Encodable {
    let id: Int
}

/// 小程序码响应。
struct TravelAlbumMpCodeResponse: Decodable {
    let mpCodeOssURL: String

    init(mpCodeOssURL: String) {
        self.mpCodeOssURL = mpCodeOssURL
    }

    enum CodingKeys: String, CodingKey {
        case mpCodeOssURL = "mp_code_oss_url"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        mpCodeOssURL = container.lossyString(forKey: .mpCodeOssURL)
    }
}

/// 有线传图页导航上下文。
struct WiredTransferContext: Hashable {
    let albumId: Int
    let albumName: String
    let phone: String
    let orderNumber: String
}

/// 有线传图上传状态。
enum WiredTransferUploadStatus: String, Codable, Equatable {
    case uploaded = "UPLOADED"
    case pending = "PENDING"
    case transferring = "TRANSFERRING"
    case uploading = "UPLOADING"
    case failed = "FAILED"

    var displayLabel: String {
        switch self {
        case .uploaded: "已上传"
        case .pending: "待上传"
        case .transferring: "传输中"
        case .uploading: "上传中"
        case .failed: "上传失败"
        }
    }
}

/// 有线传图时间侧栏时间段。
struct WiredTransferTimeSlot: Identifiable, Equatable {
    let id: String
    let dateLabel: String
    let slotStartLabel: String
    let photoCount: Int
}

/// 有线传图时间侧栏日期分组。
struct WiredTransferDateGroup: Equatable {
    let dateLabel: String
    let slots: [WiredTransferTimeSlot]
}

/// 有线传图列表分组（30 分钟时间段）。
struct WiredTransferPhotoSection: Identifiable, Equatable {
    var id: String { slotId }
    let slotId: String
    let headerTitle: String
    let photos: [WiredTransferPhotoItem]
}

/// 有线传图列表项。
struct WiredTransferPhotoItem: Identifiable, Equatable {
    let id: String
    let sourceId: String
    let fileName: String
    /// 列表使用的缩略图 URL，优先指向本地小图或远程封面。
    let thumbnailURL: String
    /// 预览使用的大图 URL，优先指向本地原图。
    let previewURL: String
    let capturedAt: String
    let fileSizeText: String
    let resolutionText: String
    let status: WiredTransferUploadStatus
    let progress: Int
    let errorMessage: String?

    init(
        id: String,
        sourceId: String? = nil,
        fileName: String,
        thumbnailURL: String,
        previewURL: String = "",
        capturedAt: String,
        fileSizeText: String,
        resolutionText: String = "",
        status: WiredTransferUploadStatus,
        progress: Int = 0,
        errorMessage: String? = nil
    ) {
        self.id = id
        self.sourceId = sourceId ?? id
        self.fileName = fileName
        self.thumbnailURL = thumbnailURL
        self.previewURL = previewURL
        self.capturedAt = capturedAt
        self.fileSizeText = fileSizeText
        self.resolutionText = resolutionText
        self.status = status
        self.progress = progress
        self.errorMessage = errorMessage
    }

    var canSelectForSpecifyUpload: Bool {
        status == .pending
    }

    var isNotUploaded: Bool {
        status == .pending || status == .failed
    }

    /// 解析拍摄时间。
    func capturedDate() -> Date? {
        Self.capturedAtFormatter.date(from: capturedAt)
    }

    /// 是否为今日拍摄。
    func isCapturedToday(calendar: Calendar = .current) -> Bool {
        guard let date = capturedDate() else { return false }
        return calendar.isDateInToday(date)
    }

    private static let capturedAtFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        return formatter
    }()
}

/// 有线传图持久化记录。
struct WiredTransferPhotoRecord: Codable, Equatable {
    let id: String
    let sourceId: String
    let fileName: String
    let localPath: String
    let thumbnailPath: String
    let capturedAt: String
    let fileSizeBytes: Int64
    let status: String
    let progress: Int
    let errorMessage: String?
    let albumId: Int
    let userId: String
    let remoteURL: String
    let updatedAt: TimeInterval

    func toPhotoItem() -> WiredTransferPhotoItem {
        let thumbnailCandidates = [thumbnailPath, remoteURL]
        let thumbnailURL = thumbnailCandidates.compactMap { path -> String? in
            guard !path.isEmpty else { return nil }
            if path.hasPrefix("http") { return path }
            let preview = CameraDownloadStorage.previewURLString(from: path)
            return preview.isEmpty ? nil : preview
        }.first ?? ""
        let previewCandidates = [localPath, remoteURL, thumbnailPath]
        let previewURL = previewCandidates.compactMap { path -> String? in
            guard !path.isEmpty else { return nil }
            if path.hasPrefix("http") { return path }
            let preview = CameraDownloadStorage.previewURLString(from: path)
            return preview.isEmpty ? nil : preview
        }.first ?? ""

        return WiredTransferPhotoItem(
            id: id,
            sourceId: sourceId,
            fileName: fileName,
            thumbnailURL: thumbnailURL,
            previewURL: previewURL,
            capturedAt: capturedAt,
            fileSizeText: Self.formatFileSize(fileSizeBytes),
            status: WiredTransferUploadStatus(rawValue: status) ?? .pending,
            progress: progress,
            errorMessage: errorMessage
        )
    }

    static func formatFileSize(_ bytes: Int64) -> String {
        guard bytes > 0 else { return "--" }
        let mb = Double(bytes) / 1024.0 / 1024.0
        if mb >= 1024 {
            return String(format: "%.2f GB", mb / 1024.0)
        }
        return String(format: "%.2f MB", mb)
    }
}

extension WiredTransferPhotoItem {
    /// 转为持久化记录。
    func toRecord(
        albumId: Int,
        userId: String,
        localPath: String,
        thumbnailPath: String = "",
        fileSizeBytes: Int64,
        remoteURL: String = ""
    ) -> WiredTransferPhotoRecord {
        WiredTransferPhotoRecord(
            id: id,
            sourceId: sourceId,
            fileName: fileName,
            localPath: localPath,
            thumbnailPath: thumbnailPath,
            capturedAt: capturedAt,
            fileSizeBytes: fileSizeBytes,
            status: status.rawValue,
            progress: progress,
            errorMessage: errorMessage,
            albumId: albumId,
            userId: userId,
            remoteURL: remoteURL,
            updatedAt: Date().timeIntervalSince1970
        )
    }
}

extension Array where Element == WiredTransferPhotoItem {
    /// 按 Tab 过滤：0 全部，1 已上传，2 失败。
    func filtered(byTabIndex index: Int) -> [WiredTransferPhotoItem] {
        switch index {
        case 1: filter { $0.status == .uploaded }
        case 2: filter { $0.status == .failed }
        default: self
        }
    }

    /// 返回各 Tab 计数。
    var transferTabCounts: [Int] {
        [count, filter { $0.status == .uploaded }.count, filter { $0.status == .failed }.count]
    }

    /// 可指定上传的 photo id 集合。
    var selectablePendingPhotoIDs: Set<String> {
        Set(filter { $0.canSelectForSpecifyUpload }.map(\.id))
    }

    /// 按拍摄时间倒序排列，最新照片在最前。
    func sortedByCaptureTimeDescending() -> [WiredTransferPhotoItem] {
        sorted { lhs, rhs in
            let leftDate = lhs.capturedDate() ?? .distantPast
            let rightDate = rhs.capturedDate() ?? .distantPast
            if leftDate != rightDate { return leftDate > rightDate }
            return lhs.id > rhs.id
        }
    }

    /// 构建 30 分钟时间段分组列表。
    func buildPhotoSections() -> [WiredTransferPhotoSection] {
        let grouped = Dictionary(grouping: compactMap { photo -> (Date, WiredTransferPhotoItem)? in
            guard let date = photo.capturedDate() else { return nil }
            return (WiredTransferTimeGrouping.halfHourSlotStart(for: date), photo)
        }) { $0.0 }

        return grouped.keys
            .sorted(by: >)
            .map { slotStart in
                let photos = (grouped[slotStart] ?? [])
                    .map(\.1)
                    .sorted { ($0.capturedDate() ?? .distantPast) > ($1.capturedDate() ?? .distantPast) }
                return WiredTransferPhotoSection(
                    slotId: WiredTransferTimeGrouping.slotID(for: slotStart),
                    headerTitle: WiredTransferTimeGrouping.sectionHeaderTitle(for: slotStart),
                    photos: photos
                )
            }
    }

    /// 构建时间侧栏分组。
    func buildSidebarGroups() -> [WiredTransferDateGroup] {
        var slotCounts: [Date: Int] = [:]
        for photo in self {
            guard let date = photo.capturedDate() else { continue }
            let slotStart = WiredTransferTimeGrouping.halfHourSlotStart(for: date)
            slotCounts[slotStart, default: 0] += 1
        }

        let calendar = Calendar.current
        let groupedByDay = Dictionary(grouping: slotCounts.keys) { calendar.startOfDay(for: $0) }

        return groupedByDay.keys
            .sorted(by: >)
            .map { day in
                let slots = (groupedByDay[day] ?? [])
                    .sorted(by: >)
                    .compactMap { slotStart -> WiredTransferTimeSlot? in
                        guard let count = slotCounts[slotStart] else { return nil }
                        return WiredTransferTimeSlot(
                            id: WiredTransferTimeGrouping.slotID(for: slotStart),
                            dateLabel: WiredTransferTimeGrouping.dateLabel(for: slotStart),
                            slotStartLabel: WiredTransferTimeGrouping.timeLabel(for: slotStart),
                            photoCount: count
                        )
                    }
                return WiredTransferDateGroup(
                    dateLabel: WiredTransferTimeGrouping.dateLabel(for: day),
                    slots: slots
                )
            }
    }
}

/// 有线传图 30 分钟时间段分组工具。
enum WiredTransferTimeGrouping {
    /// 计算 30 分钟时间段起点。
    static func halfHourSlotStart(for date: Date, calendar: Calendar = .current) -> Date {
        var components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
        let minute = (components.minute ?? 0) / 30 * 30
        components.minute = minute
        components.second = 0
        components.nanosecond = 0
        return calendar.date(from: components) ?? date
    }

    /// 时间段唯一标识。
    static func slotID(for slotStart: Date) -> String {
        slotIDFormatter.string(from: slotStart)
    }

    /// 分组标题，例如「05月20日 12:00 - 12:30」。
    static func sectionHeaderTitle(for slotStart: Date) -> String {
        "\(dateLabel(for: slotStart)) \(timeLabel(for: slotStart)) - \(timeLabel(for: slotStart.addingTimeInterval(30 * 60)))"
    }

    /// 日期标签，例如「05月20日」。
    static func dateLabel(for date: Date) -> String {
        dateLabelFormatter.string(from: date)
    }

    /// 时间标签，例如「12:05」。
    static func timeLabel(for date: Date) -> String {
        timeLabelFormatter.string(from: date)
    }

    private static let slotIDFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm"
        return formatter
    }()

    private static let dateLabelFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.dateFormat = "MM月dd日"
        return formatter
    }()

    private static let timeLabelFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.dateFormat = "HH:mm"
        return formatter
    }()
}

private extension KeyedDecodingContainer {
    func lossyString(forKey key: Key) -> String {
        if let value = try? decode(String.self, forKey: key) { return value }
        if let value = try? decode(Int.self, forKey: key) { return "\(value)" }
        if let value = try? decode(Double.self, forKey: key) { return "\(value)" }
        return ""
    }

    func lossyInt(forKey key: Key) -> Int {
        if let value = try? decode(Int.self, forKey: key) { return value }
        if let value = try? decode(String.self, forKey: key), let intValue = Int(value) { return intValue }
        if let value = try? decode(Double.self, forKey: key) { return Int(value) }
        return 0
    }

    func lossyBool(forKey key: Key) -> Bool {
        if let value = try? decode(Bool.self, forKey: key) { return value }
        if let value = try? decode(Int.self, forKey: key) { return value != 0 }
        if let value = try? decode(String.self, forKey: key) {
            return value == "1" || value.lowercased() == "true"
        }
        return false
    }
}
