import Foundation
import SwiftData

@MainActor
final class ICloudSyncService {
    static let shared = ICloudSyncService()

    private let store = NSUbiquitousKeyValueStore.default
    private let payloadKey = "mealmood_sync_payload_v1"
    private let lastAppliedKey = "mealmood_sync_last_applied"

    private var isApplyingRemote = false

    private init() {}

    func pullRemoteIfNeeded(context: ModelContext) async {
        store.synchronize()

        guard let data = store.data(forKey: payloadKey),
              let snapshot = try? JSONDecoder().decode(SyncSnapshot.self, from: data) else {
            return
        }

        let lastApplied = UserDefaults.standard.double(forKey: lastAppliedKey)
        let remoteTs = snapshot.updatedAt.timeIntervalSince1970
        if remoteTs <= lastApplied {
            return
        }

        isApplyingRemote = true
        defer { isApplyingRemote = false }

        apply(snapshot: snapshot, context: context)
        UserDefaults.standard.set(remoteTs, forKey: lastAppliedKey)
    }

    func pushLocalSnapshot(context: ModelContext) async {
        if isApplyingRemote { return }
        guard let snapshot = makeSnapshot(context: context) else { return }
        guard let data = try? JSONEncoder().encode(snapshot) else { return }

        store.set(data, forKey: payloadKey)
        store.synchronize()
        UserDefaults.standard.set(snapshot.updatedAt.timeIntervalSince1970, forKey: lastAppliedKey)
    }

    private func makeSnapshot(context: ModelContext) -> SyncSnapshot? {
        let settings = (try? context.fetch(FetchDescriptor<AppSettings>()))?.first
        let tags = (try? context.fetch(FetchDescriptor<Tag>())) ?? []
        let dishes = (try? context.fetch(FetchDescriptor<Dish>())) ?? []
        let plans = (try? context.fetch(FetchDescriptor<WeekPlan>())) ?? []

        guard settings != nil || !tags.isEmpty || !dishes.isEmpty || !plans.isEmpty else {
            return nil
        }

        let snapshot = SyncSnapshot(
            updatedAt: Date(),
            settings: settings.map {
                SettingsPayload(
                    mealWindows: $0.mealWindows,
                    includeWeekends: $0.includeWeekends,
                    language: $0.language,
                    calendarId: $0.calendarId,
                    syncEnabled: $0.syncEnabled,
                    syncMode: $0.syncMode,
                    lunchTime: $0.lunchTime,
                    dinnerTime: $0.dinnerTime,
                    eventDuration: $0.eventDuration,
                    eventPrefix: $0.eventPrefix,
                    reminderMinutesBefore: $0.reminderMinutesBefore,
                    iCloudSyncEnabled: $0.iCloudSyncEnabled,
                    isPremium: $0.isPremium,
                    onboardingCompleted: $0.onboardingCompleted
                )
            },
            tags: tags.map {
                TagPayload(
                    id: $0.id,
                    name: $0.name,
                    nameEN: $0.nameEN,
                    color: $0.color,
                    maxPerWeek: $0.maxPerWeek,
                    noConsecutive: $0.noConsecutive,
                    noDuplicateInDay: $0.noDuplicateInDay,
                    mealTypeRestriction: $0.mealTypeRestriction,
                    isDefault: $0.isDefault,
                    sortOrder: $0.sortOrder
                )
            },
            dishes: dishes.map {
                DishPayload(
                    id: $0.id,
                    name: $0.name,
                    descriptionText: $0.descriptionText,
                    tagIds: $0.tagIds,
                    createdAt: $0.createdAt
                )
            },
            weekPlans: plans.map { plan in
                WeekPlanPayload(
                    id: plan.id,
                    weekStartDate: plan.weekStartDate,
                    createdAt: plan.createdAt,
                    updatedAt: plan.updatedAt,
                    slots: plan.slots.map {
                        MealSlotPayload(
                            id: $0.id,
                            dayOfWeek: $0.dayOfWeek,
                            mealType: $0.mealType,
                            dishId: $0.dishId,
                            calendarEventId: $0.calendarEventId,
                            isRuleOverridden: $0.isRuleOverridden
                        )
                    }
                )
            }
        )

        return snapshot
    }

    private func apply(snapshot: SyncSnapshot, context: ModelContext) {
        guard snapshot.settings != nil || !snapshot.tags.isEmpty || !snapshot.dishes.isEmpty || !snapshot.weekPlans.isEmpty else {
            return
        }

        let existingSlots = (try? context.fetch(FetchDescriptor<MealSlot>())) ?? []
        let existingPlans = (try? context.fetch(FetchDescriptor<WeekPlan>())) ?? []
        let existingDishes = (try? context.fetch(FetchDescriptor<Dish>())) ?? []
        let existingTags = (try? context.fetch(FetchDescriptor<Tag>())) ?? []
        let existingSettings = (try? context.fetch(FetchDescriptor<AppSettings>())) ?? []

        existingSlots.forEach { context.delete($0) }
        existingPlans.forEach { context.delete($0) }
        existingDishes.forEach { context.delete($0) }
        existingTags.forEach { context.delete($0) }
        existingSettings.forEach { context.delete($0) }

        if let settingsPayload = snapshot.settings {
            let settings = AppSettings()
            settings.mealWindows = settingsPayload.mealWindows
            settings.includeWeekends = settingsPayload.includeWeekends
            settings.language = settingsPayload.language
            settings.calendarId = settingsPayload.calendarId
            settings.syncEnabled = settingsPayload.syncEnabled
            settings.syncMode = settingsPayload.syncMode
            settings.lunchTime = settingsPayload.lunchTime
            settings.dinnerTime = settingsPayload.dinnerTime
            settings.eventDuration = settingsPayload.eventDuration
            settings.eventPrefix = settingsPayload.eventPrefix
            settings.reminderMinutesBefore = settingsPayload.reminderMinutesBefore
            settings.iCloudSyncEnabled = settingsPayload.iCloudSyncEnabled
            settings.isPremium = settingsPayload.isPremium
            settings.onboardingCompleted = settingsPayload.onboardingCompleted
            context.insert(settings)
        }

        snapshot.tags.forEach { payload in
            let tag = Tag(
                id: payload.id,
                name: payload.name,
                nameEN: payload.nameEN,
                color: payload.color,
                maxPerWeek: payload.maxPerWeek,
                noConsecutive: payload.noConsecutive,
                noDuplicateInDay: payload.noDuplicateInDay,
                mealTypeRestriction: payload.mealTypeRestriction,
                isDefault: payload.isDefault,
                sortOrder: payload.sortOrder
            )
            context.insert(tag)
        }

        snapshot.dishes.forEach { payload in
            let dish = Dish(
                id: payload.id,
                name: payload.name,
                descriptionText: payload.descriptionText,
                tagIds: payload.tagIds,
                createdAt: payload.createdAt
            )
            context.insert(dish)
        }

        snapshot.weekPlans.forEach { payload in
            let plan = WeekPlan(
                id: payload.id,
                weekStartDate: payload.weekStartDate,
                slots: [],
                createdAt: payload.createdAt,
                updatedAt: payload.updatedAt
            )
            context.insert(plan)

            payload.slots.forEach { slotPayload in
                let slot = MealSlot(
                    id: slotPayload.id,
                    dayOfWeek: slotPayload.dayOfWeek,
                    mealType: slotPayload.mealType,
                    dishId: slotPayload.dishId,
                    calendarEventId: slotPayload.calendarEventId,
                    isRuleOverridden: slotPayload.isRuleOverridden
                )
                slot.weekPlan = plan
                plan.slots.append(slot)
                context.insert(slot)
            }
        }

        try? context.save()
    }
}

private struct SyncSnapshot: Codable {
    let updatedAt: Date
    let settings: SettingsPayload?
    let tags: [TagPayload]
    let dishes: [DishPayload]
    let weekPlans: [WeekPlanPayload]
}

private struct SettingsPayload: Codable {
    let mealWindows: String
    let includeWeekends: Bool
    let language: String
    let calendarId: String?
    let syncEnabled: Bool
    let syncMode: String?
    let lunchTime: Date
    let dinnerTime: Date
    let eventDuration: Int
    let eventPrefix: String
    let reminderMinutesBefore: Int?
    let iCloudSyncEnabled: Bool?
    let isPremium: Bool
    let onboardingCompleted: Bool
}

private struct TagPayload: Codable {
    let id: UUID
    let name: String
    let nameEN: String
    let color: String
    let maxPerWeek: Int?
    let noConsecutive: Bool
    let noDuplicateInDay: Bool
    let mealTypeRestriction: String?
    let isDefault: Bool
    let sortOrder: Int
}

private struct DishPayload: Codable {
    let id: UUID
    let name: String
    let descriptionText: String?
    let tagIds: [UUID]
    let createdAt: Date
}

private struct WeekPlanPayload: Codable {
    let id: UUID
    let weekStartDate: Date
    let createdAt: Date
    let updatedAt: Date
    let slots: [MealSlotPayload]
}

private struct MealSlotPayload: Codable {
    let id: UUID
    let dayOfWeek: Int
    let mealType: String
    let dishId: UUID?
    let calendarEventId: String?
    let isRuleOverridden: Bool
}
