import Combine
import Foundation
import Segment
import SwiftUI

public struct DebugAnalyticsEvent: Equatable, Identifiable {
    public static func == (lhs: DebugAnalyticsEvent, rhs: DebugAnalyticsEvent) -> Bool {
        lhs.id == rhs.id
    }

    public enum DataKey {
        public static let event = "event"
        public static let name = "name"
        public static let date = "date"
        public static let properties = "properties"

        public enum SubKey {
            public static let actionName = "actionName"
        }
    }

    public static let debugNotificationName = Notification.Name("DebugTrackingNotification")

    public static func postDebugNotification(_ event: DebugAnalyticsEvent) {
        NotificationCenter.default.post(
            name: debugNotificationName,
            object: nil,
            userInfo: event.wrappedInDictionary
        )
    }

    public var id = UUID()
    public let date: Date
    public let name: String
    public let properties: [String: Any]
    public private(set) var actionName: String

    public init(name: String, properties: Codable, date: Date = .now) {
        self.date = date
        self.name = name
        self.properties = (try? JSON(properties).dictionaryValue) ?? [:]
        self.actionName = ""

        if let actionNameProperty = self.properties[DataKey.SubKey.actionName] as? String {
            self.actionName = actionNameProperty
        }
    }

    static func unwrapFromNotificationDictionary(_ dict: [String: Any]) -> DebugAnalyticsEvent? {
        dict[DataKey.event] as? DebugAnalyticsEvent
    }

    var wrappedInDictionary: [String: Any] {
        [DataKey.event: self]
    }

    public var formattedDate: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd' | 'HH:mm:ss.SSS Z"
        return dateFormatter.string(from: date)
    }

    public var asDictionary: [String: Any] {
        [
            DataKey.name: name,
            DataKey.date: formattedDate,
            DataKey.properties: properties,
        ]
    }

    public var propertiesAsString: String {
        let sortedKeys = properties.keys.sorted { $0 < $1 }

        return sortedKeys.map { key in
            guard let value = properties[key] else { return "" }
            switch value {
            case let string as String:
                return "\(key): \(string)"
            case let int as Int:
                return "\(key): \(int)"
            case let double as Double:
                return "\(key): \(double)"
            case let bool as Bool:
                return "\(key): \(bool)"
            default:
                return "\(key): \(value)"
            }
        }
        .joined(separator: "\n")
    }
}

struct DebugNotificationModifier: ViewModifier {
    let notificationName: Notification.Name
    let onReceive: (DebugAnalyticsEvent) -> Void

    func body(content: Content) -> some View {
        content
            .onAppear {
                NotificationCenter.default.addObserver(
                    forName: notificationName,
                    object: nil,
                    queue: .main
                ) { notification in
                    guard
                        let userInfo = notification.userInfo as? [String: Any],
                        let debugEvent = DebugAnalyticsEvent.unwrapFromNotificationDictionary(userInfo)
                    else { return }
                    onReceive(debugEvent)
                }
            }
            .onDisappear {
                NotificationCenter.default.removeObserver(self, name: notificationName, object: nil)
            }
    }
}

public extension View {
    func onAnalyticsTrackingDebugNotification(perform: @escaping (DebugAnalyticsEvent) -> Void) -> some View {
        modifier(DebugNotificationModifier(notificationName: DebugAnalyticsEvent.debugNotificationName, onReceive: perform))
    }
}
