//
//  Item.swift
//  NearFuture
//
//  Created by neon443 on 24/12/2024.
//

import Foundation
import SwiftData
import SwiftUI
import WidgetKit
import UserNotifications
import AppIntents

//@Model
//final class Item {
//    var timestamp: Date
//
//    init(timestamp: Date) {
//        self.timestamp = timestamp
//    }
//}

struct Event: Identifiable, Codable, Equatable, Animatable {
	var id = UUID()
	var name: String
	var complete: Bool
	var completeDesc: String
	var symbol: String
	var color: ColorCodable
	var notes: String
	var date: Date
	var recurrence: RecurrenceType
	
	enum RecurrenceType: String, Codable, CaseIterable {
		case none, daily, weekly, monthly, yearly
	}
}

struct ColorCodable: Codable, Equatable {
	init(_ color: Color) {
		let uiColor = UIColor(color)
		var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
		uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
		
		self.red = Double(r)
		self.green = Double(g)
		self.blue = Double(b)
	}
	init(uiColor: UIColor) {
		var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
		uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
		self.red = Double(r)
		self.green = Double(g)
		self.blue = Double(b)
	}
	init(red: Double, green: Double, blue: Double) {
		self.red = red
		self.green = green
		self.blue = blue
	}
	
	var red: Double
	var green: Double
	var blue: Double
	
	var color: Color {
		Color(red: red, green: green, blue: blue)
	}
	var colorBind: Color {
		get {
			return Color(
				red: red,
				green: green,
				blue: blue
			)
		} set {
			let cc = ColorCodable(newValue)
			self.red = cc.red
			self.green = cc.green
			self.blue = cc.blue
		}
	}
}

func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
	let calendar = Calendar.current
	let startOfDayNow = calendar.startOfDay(for: Date())
	let startOfDayEvent = calendar.startOfDay(for: eventDate)
	let components = calendar.dateComponents([.day], from: startOfDayNow, to: startOfDayEvent)
	guard let days = components.day else { return ("N/A", "N/A") }
	guard days != 0 else { return ("Today", "Today") }
	if days < 0 {
		//past
		return (
			"\(-days) day\(plu(days)) ago",
			"\(days)d"
		)
	} else {
		//future
		return (
			"\(days) day\(plu(days))",
			"\(days)d"
		)
	}
}

struct Settings: Codable, Equatable {
	var showCompletedInHome: Bool
	var tint: ColorCodable
	var showWhatsNew: Bool
	var prevAppVersion: String
}

struct AccentIcon {
	var icon: UIImage
	var color: Color
	var name: String
	init(_ colorName: String) {
		if colorName == "orange" {
			self.icon = UIImage(named: "AppIcon")!
		} else {
			self.icon = UIImage(named: colorName)!
		}
		self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!)
		self.name = colorName
	}
}

class SettingsViewModel: ObservableObject {
	@Published var settings: Settings = Settings(
		showCompletedInHome: false,
		tint: ColorCodable(uiColor: UIColor(named: "AccentColor")!),
		showWhatsNew: true,
		prevAppVersion: getVersion()+getBuildID()
	)
	@Published var notifsGranted: Bool = false
	
	@Published var colorChoices: [AccentIcon] = []
	
	let accentChoices: [String] = [
		"red",
		"orange",
		"yellow",
		"green",
		"blue",
		"bloo",
		"purple",
		"pink"
	]
	
	@Published var device: (sf: String, label: String)
	
	init(load: Bool = true) {
		self.device = getDevice()
		if load {
			loadSettings()
			Task {
				let requestResult = await requestNotifs()
				await MainActor.run {
					self.notifsGranted = requestResult
				}
			}
		}
	}
	
	func changeTint(to: String) {
		if let uicolor = UIColor(named: "uiColors/\(to)") {
			self.settings.tint = ColorCodable(uiColor: uicolor)
			saveSettings()
		}
	}
	
	let appGroupSettingsStore = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard
	let icSettStore = NSUbiquitousKeyValueStore.default
	
	func loadSettings() {
		let decoder = JSONDecoder()
		if let icSettings = icSettStore.data(forKey: "settings") {
			if let decodedSetts = try? decoder.decode(Settings.self, from: icSettings) {
				self.settings = decodedSetts
			}
		} else if let savedData = appGroupSettingsStore.data(forKey: "settings") {
			if let decodedSetts = try? decoder.decode(Settings.self, from: savedData) {
				self.settings = decodedSetts
			}
		}
		if self.settings.prevAppVersion != getVersion()+getBuildID() {
			self.settings.showWhatsNew = true
		}
	}
	
	func saveSettings() {
		let encoder = JSONEncoder()
		if let encoded = try? encoder.encode(settings) {
			appGroupSettingsStore.set(encoded, forKey: "settings")
			icSettStore.set(encoded, forKey: "settings")
			icSettStore.synchronize()
			loadSettings()
		}
	}
}

class EventViewModel: ObservableObject, @unchecked Sendable {
	@Published var events: [Event] = []
	@Published var icloudData: [Event] = []
	
	public let template: Event = Event(
		name: "",
		complete: false,
		completeDesc: "",
		symbol: "star",
		color: ColorCodable(randomColor()),
		notes: "",
		date: Date(),
		recurrence: .none
	)
	@Published var editableTemplate: Event
	@Published var example: Event = Event(
		name: "event",
		complete: false,
		completeDesc: "dofajiof",
		symbol: "star",
		color: ColorCodable(.orange),
		notes: "lksdjfakdflkasjlkjl",
		date: Date(),
		recurrence: .daily
	)
	
	@Published var lastSync: Date? = nil
	@Published var icloudEventCount: Int = 0
	@Published var localEventCount: Int = 0
	@Published var syncStatus: String = "Not Synced"
	
	init(load: Bool = true) {
		self.editableTemplate = template
		if load {
			loadEvents()
		}
	}
	
	//appgroup or regular userdefaults
	let appGroupUserDefaults = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard
	
	//icloud store
	let icloudStore = NSUbiquitousKeyValueStore.default
	
	// load from icloud or local
	func loadEvents() {
		//load icloud 1st
		if let icData = icloudStore.data(forKey: "events") {
			let decoder = JSONDecoder()
			if let decodedIcEvents = try? decoder.decode([Event].self, from: icData) {
				self.icloudData = decodedIcEvents
				self.events = decodedIcEvents
			}
		}
		
		if events.isEmpty, let savedData = appGroupUserDefaults.data(forKey: "events") {
			let decoder = JSONDecoder()
			if let decodedEvents = try? decoder.decode([Event].self, from: savedData) {
				self.events = decodedEvents
			}
		}
		updateSyncStatus()
		self.events.sort() {$0.date < $1.date}
	}
	
	func getNotifs() async -> [UNNotificationRequest] {
		return await UNUserNotificationCenter.current().pendingNotificationRequests()
	}
	
	func checkPendingNotifs(_ pending: [UNNotificationRequest]) {
		var eventUUIDs = events.map({$0.id.uuidString})
		for req in pending {
			//match the notif to an event
			if let index = events.firstIndex(where: {$0.id.uuidString == req.identifier}) {
				if let remove = eventUUIDs.firstIndex(where: {$0 == req.identifier}) {
					eventUUIDs.remove(at: remove)
				}
				let components = getDateComponents(events[index].date)
				//check the notif matches event details
				if req.content.title == events[index].name,
				   req.content.subtitle == events[index].notes,
				   req.trigger == UNCalendarNotificationTrigger(dateMatching: components, repeats: false) {
					//if it does, make sure the notif delets if u complete the veent
					if events[index].complete {
						cancelNotif(req.identifier)
					}
				} else {
					cancelNotif(req.identifier)
					scheduleEventNotif(events[index])
				}
			} else {
				//cancel if the event is deleted
				cancelNotif(req.identifier)
			}
		}
		for uuid in eventUUIDs {
			if let event = events.first(where: {$0.id.uuidString == uuid}) {
				scheduleEventNotif(event)
			}
		}
	}
	
	// save to local and icloud
	func saveEvents() {
		let encoder = JSONEncoder()
		if let encoded = try? encoder.encode(events) {
			appGroupUserDefaults.set(encoded, forKey: "events")
			
			//sync
			icloudStore.set(encoded, forKey: "events")
			icloudStore.synchronize()
			
			updateSyncStatus()
			loadEvents()
			Task {
				await checkPendingNotifs(getNotifs())
			}
			WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events
			objectWillChange.send()
		}
	}
	
	private func updateSyncStatus() {
		lastSync = Date()
		icloudEventCount = icloudData.count
		localEventCount = events.count
		
		if icloudEventCount == localEventCount {
			syncStatus = "Successful"
		} else {
			syncStatus = "Pending"
		}
	}
	
	func addEvent(newEvent: Event) {
		events.append(newEvent)
		scheduleEventNotif(newEvent)
		saveEvents() //sync with icloud
	}
	
	func removeEvent(at index: IndexSet) {
		events.remove(atOffsets: index)
		saveEvents() //sync local and icl
	}
	
	func hasUbiquitousKeyValueStore() -> Bool {
		let icloud = NSUbiquitousKeyValueStore.default
		
		let key = "com.neon443.NearFuture.testkey"
		let value = "testValue"
		
		icloud.set(value, forKey: key)
		icloud.synchronize()
		
		if icloud.string(forKey: key) != nil {
			icloud.removeObject(forKey: key)
			icloud.synchronize()
			return true
		} else {
			print("!has UbiquitousKeyValueStore")
			icloud.removeObject(forKey: key)
			icloud.synchronize()
			return false
		}
	}
	
	func sync() {
		NSUbiquitousKeyValueStore.default.synchronize()
		loadEvents()
	}
	
	func replaceLocalWithiCloudData() {
		icloudStore.synchronize()
		self.events = self.icloudData
		saveEvents()
	}
	
	func replaceiCloudWithLocalData() {
		icloudStore.synchronize()
		self.icloudData = self.events
		saveEvents()
	}
	
	func exportEvents() -> String {
		let encoder = JSONEncoder()
		if let json = try? encoder.encode(self.events) {
			return "\(json.base64EncodedString())"
		}
		return ""
	}
	
	func importEvents(_ imported: String) throws {
		guard let data = Data(base64Encoded: imported) else {
			throw importError.invalidB64
		}
		let decoder = JSONDecoder()
		do {
			let decoded = try decoder.decode([Event].self, from: data)
			self.events = decoded
			saveEvents()
		} catch {
			throw error
		}
	}
	
	//MARK: Danger Zone
	func dangerClearLocalData() {
		UserDefaults.standard.removeObject(forKey: "events")
		appGroupUserDefaults.removeObject(forKey: "events")
		events.removeAll()
		cancelAllNotifs()
		updateSyncStatus()
	}
	
	func dangerCleariCloudData() {
		icloudStore.removeObject(forKey: "events")
		icloudStore.synchronize()
		icloudData.removeAll()
		cancelAllNotifs()
		updateSyncStatus()
	}
	
	func dangerResetLocalData() {
		let userDFDict = UserDefaults.standard.dictionaryRepresentation()
		for key in userDFDict.keys {
			UserDefaults.standard.removeObject(forKey: key)
		}
		
		let appGUSDDict = appGroupUserDefaults.dictionaryRepresentation()
		for key in appGUSDDict.keys {
			appGroupUserDefaults.removeObject(forKey: key)
		}
		
		events.removeAll()
		cancelAllNotifs()
		updateSyncStatus()
	}
	
	func dangerResetiCloud() {
		let icloudDict = icloudStore.dictionaryRepresentation
		for key in icloudDict.keys {
			icloudStore.removeObject(forKey: key)
		}
		icloudStore.synchronize()
		icloudData.removeAll()
		cancelAllNotifs()
		updateSyncStatus()
	}
}

class dummyEventViewModel: EventViewModel, @unchecked Sendable {
	var template2: Event
	override init(load: Bool = false) {
		self.template2 = Event(
			name: "template2",
			complete: false,
			completeDesc: "",
			symbol: "hammer",
			color: ColorCodable(randomColor()),
			notes: "notes",
			date: Date(),
			recurrence: .none
		)
		super.init(load: false)
		self.events = [self.example, self.template, self.template2]
		self.events[0].complete.toggle()
	}
}

class dummySettingsViewModel: SettingsViewModel {
	override init(load: Bool = false) {
		super.init(load: false)
	}
}

func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String {
	let dateString = date.formatted(date: .long, time: .omitted)
	let recurrenceDescription: String
	
	switch recurrence {
	case .none:
		recurrenceDescription = "Occurs once on"
	case .daily:
		recurrenceDescription = "Repeats every day from"
	case .weekly:
		recurrenceDescription = "Repeats every week from"
	case .monthly:
		recurrenceDescription = "Repeats every month from"
	case .yearly:
		recurrenceDescription = "Repeats every year from"
	}
	
	return "\(recurrenceDescription) \(dateString)"
}

func randomRainbowColor() -> Color {
	return [
		Color.red,
		Color.orange,
		Color.yellow,
		Color.green,
		Color.blue,
		Color.indigo,
		Color.purple
	].randomElement()!
}

func randomColor() -> Color {
	let r = Double.random(in: 0...1)
	let g = Double.random(in: 0...1)
	let b = Double.random(in: 0...1)
	return Color(red: r, green: g, blue: b)
}

func plu(_ inp: Int) -> String {
	var input = inp
	if inp < 0 { input.negate() }
	return "\(input == 1 ? "" : "s")"
}

public enum importError: Error {
	case invalidB64
}

func requestNotifs() async -> Bool {
	let result = try? await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .carPlay, .sound])
	return result ?? false
}

func scheduleNotif(title: String, sub: String, date: Date, id: String = UUID().uuidString) {
	let content = UNMutableNotificationContent()
	content.title = title
	content.subtitle = sub
	content.sound = .default
	
	let identifier = id
	let dateComponents = getDateComponents(date)
	
	let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
	let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
	UNUserNotificationCenter.current().add(request)
}

func scheduleEventNotif(_ event: Event) {
	scheduleNotif(
		title: event.name,
		sub: event.notes,
		date: event.date,
		id: event.id.uuidString
	)
}

func getDateComponents(_ date: Date) -> DateComponents {
	return Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: date)
}

func cancelNotif(_ id: String) {
	UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [id])
}

func cancelAllNotifs() {
	UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
}

func getVersion() -> String {
	guard let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] else {
		fatalError("no bundle id wtf lol")
	}
	return "\(version)"
}

func getBuildID() -> String {
	guard let build = Bundle.main.infoDictionary?["CFBundleVersion"] else {
		fatalError("wtf did u do w the build number")
	}
	return "\(build)"
}

func getDevice() -> (sf: String, label: String) {
	let asi = ProcessInfo().isiOSAppOnMac
	let model = UIDevice().model
	if asi {
		return (sf: "laptopcomputer", label: "Computer")
	} else if model == "iPhone" {
		return (sf: model.lowercased(), label: model)
	} else if model == "iPad" {
		return (sf: model.lowercased(), label: model)
	}
	return (sf: "iphone", label: "iPhone")
}

extension Event: AppEntity {
	static let defaultQuery = EventQuery()
	
	static var typeDisplayRepresentation: TypeDisplayRepresentation {
		TypeDisplayRepresentation("skdfj")
	}
	
	var displayRepresentation: DisplayRepresentation {
		DisplayRepresentation("eventsss")
	}
}

struct EventQuery: EntityQuery, DynamicOptionsProvider {
	typealias Entity = Event
	@Dependency var vm: EventViewModel
	func results() async throws -> some ResultsCollection {
		return vm.events
	}
	//	func defaultResult() async -> DefaultValue? {
	//		return vm.events[0]
	//	}
	func entities(for identifiers: [Entity.ID]) async throws -> [Entity] {
		return vm.events
	}
	func suggestedEntities() async throws -> some ResultsCollection {
		return vm.events //lol cba
	}
}

struct CompleteEvent: AppIntent {
	static var title: LocalizedStringResource = "Complete An Event"
	static var description = IntentDescription("Mark an Event as complete.")
	
	@Parameter(title: "Event ID")
	var eventID: String
	
	func perform() async throws -> some IntentResult {
		print("s")
		let viewModel = EventViewModel()
		print("hip")
		guard let eventUUID = UUID(uuidString: eventID) else {
			print(":sdklfajk")
			return .result()
		}
		print("hii")
		if let eventToModify = viewModel.events.firstIndex(where: { $0.id == eventUUID }) {
			print("hiii")
			viewModel.events[eventToModify].complete = true
			viewModel.saveEvents()
		}
		return .result()
	}
}
