import APIClient
import Foundation
import SwiftData

public enum InAppNotificationItem: Equatable, Identifiable, Expirable, Codable {
    public enum RedirectStyle {
        public enum RedirectOptions: Hashable {
            case openComments
            case openCommentsForReplyToComment(_ commentId: String)
        }

        case noRedirect
        case toClip(_ clipID: String, _ options: Set<RedirectOptions>)
        case toProfile(_ handle: String)
        case toProfileList(_ handleList: [MiniProfileV2])
        case toPlaylist(_ playlistID: String)
        case toExternalURL(_ url: String)
        case toHook(_ hookID: String, _ options: Set<RedirectOptions>)
    }

    case v1(NotificationItem)
    case v2(NotificationItemV2)

    public var storageVersionIdentifier: String {
        switch self {
        case .v1:
            return "v1"
        case .v2:
            return "v2"
        }
    }

    public var id: String {
        switch self {
        case .v1(let item):
            return item.id
        case .v2(let item):
            return item.id
        }
    }

    public var isExpired: Bool {
        switch self {
        case .v1(let item):
            return item.isExpired
        case .v2(let item):
            return item.isExpired
        }
    }

    public var createdAt: Date {
        switch self {
        case .v1(let notificationItem):
            return notificationItem.createdAt
        case .v2(let notificationItemV2):
            return notificationItemV2.updatedAt
        }
    }

    public var notificationType: NotificationType {
        switch self {
        case .v1(let notificationItem):
            return notificationItem.notificationType
        case .v2(let notificationItemV2):
            return notificationItemV2.notificationType
        }
    }

    public var isRead: Bool {
        switch self {
        case .v1(let notificationItem):
            return notificationItem.isRead
        case .v2(let notificationItemV2):
            return notificationItemV2.isRead
        }
    }

    public var isUnread: Bool {
        guard notificationType.canBeRead else { return false }
        return !isRead
    }

    public var hasUserProfiles: Bool {
        switch self {
        case .v1(let item):
            return !item.userProfiles.isEmpty
        case .v2(let item):
            return !item.userProfiles.isEmpty
        }
    }

    public var firstProfile: Bool {
        switch self {
        case .v1(let item):
            return !item.userProfiles.isEmpty
        case .v2(let item):
            return !item.userProfiles.isEmpty
        }
    }

    public func updatedUserProfile(isFollowing: Bool, handle: String) -> InAppNotificationItem {
        switch self {
        case .v1(let item):
            var mutable = item
            if let index = item.userProfiles.firstIndex(where: { $0.handle == handle }) {
                mutable.userProfiles[index].isFollowing = isFollowing
            }
            return .v1(mutable)

        case .v2(let item):
            var mutable = item
            if let index = item.userProfiles.firstIndex(where: { $0.handle == handle }) {
                mutable.userProfiles[index].isFollowing = isFollowing
            }
            return .v2(mutable)
        }
    }

    public var primaryRedirectStyle: RedirectStyle {
        guard case .v2(let item) = self else { return .noRedirect }

        switch notificationType {
        case .announcement:
            return .toExternalURL(item.redirectUrl ?? "")

        case .clipLike, .sceneLike, .clipCreateFollowee, .clipRemix:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toClip(contentID, [])

        case .clipComment,
             .commentLike,
             .commentReply,
             .sceneComment:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toClip(contentID, [.openComments])

        case .commentMention:
            guard let contentID = item.contentId else { return .noRedirect }
            if let commentId = item.contentAncillaryId {
                return .toClip(contentID, [.openCommentsForReplyToComment(commentId)])
            } else {
                return .toClip(contentID, [.openComments])
            }

        case .captionMention:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toClip(contentID, [.openComments])

        case .defaultValue:
            guard let redirectURL = item.redirectUrl else { return .noRedirect }
            return .toExternalURL(redirectURL)

        case .follow:
            guard !item.userProfiles.isEmpty else { return .noRedirect }
            if item.userProfiles.count == 1, let userHandle = item.userProfiles[0].handle {
                return .toProfile(userHandle)
            } else {
                return .toProfileList(item.userProfiles)
            }

        case .playlistComment:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toPlaylist(contentID)

        case .playlistLike:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toPlaylist(contentID)

        case .hookLike,
             .hookCreated:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toHook(contentID, [])

        case .hookCommentLike,
             .hookCommentReply:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toHook(contentID, [.openComments])

        case .hookComment,
             .hookCommentMention:
            guard let contentID = item.contentId else { return .noRedirect }
            if let commentId = item.contentAncillaryId {
                return .toHook(contentID, [.openCommentsForReplyToComment(commentId)])
            } else {
                return .toHook(contentID, [.openComments])
            }

        case .videoCoverHookLike:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toHook(contentID, [])

        case .videoCoverHookCommentLike,
             .videoCoverHookCommentReply:
            guard let contentID = item.contentId else { return .noRedirect }
            return .toHook(contentID, [.openComments])

        case .videoCoverHookComment,
             .videoCoverHookCommentMention:
            guard let contentID = item.contentId else { return .noRedirect }
            if let commentId = item.contentAncillaryId {
                return .toHook(contentID, [.openCommentsForReplyToComment(commentId)])
            } else {
                return .toHook(contentID, [.openComments])
            }

        case .clipUnlike,
             .codeRedeem,
             .delete,
             .invite,
             .inviteAccepted,
             .personaFavorite,
             .personaFollow,
             .personaUsed,
             .playlistUnlike,
             .sceneUnlike,
             .unfollow,
             .restrictedByDefault:
            return .noRedirect
        }
    }

    public var profileRedirectStyle: RedirectStyle {
        guard case .v2(let item) = self else { return .noRedirect }

        switch notificationType {
        case .announcement, .defaultValue:
            /* Supported types with no expected profile redirects */
            return .noRedirect

        case .clipLike,
             .sceneLike,
             .clipComment,
             .commentLike,
             .commentReply,
             .commentMention,
             .captionMention,
             .sceneComment,
             .follow,
             .hookComment,
             .hookCommentLike,
             .hookCommentMention,
             .hookCommentReply,
             .hookCreated,
             .hookLike,
             .videoCoverHookLike,
             .videoCoverHookComment,
             .videoCoverHookCommentLike,
             .videoCoverHookCommentMention,
             .videoCoverHookCommentReply,
             .playlistComment,
             .playlistLike,
             .clipCreateFollowee,
             .clipRemix:
            guard !item.userProfiles.isEmpty else { return .noRedirect }
            if item.userProfiles.count == 1, let userHandle = item.userProfiles[0].handle {
                return .toProfile(userHandle)
            } else {
                return .toProfileList(item.userProfiles)
            }

        case .clipUnlike,
             .codeRedeem,
             .delete,
             .invite,
             .inviteAccepted,
             .personaFavorite,
             .personaFollow,
             .personaUsed,
             .playlistUnlike,
             .sceneUnlike,
             .unfollow,
             .restrictedByDefault:
            /* Unsupported types with no expected profile redirects */
            return .noRedirect
        }
    }

    /*
        Associated enums don't fit into CoreData/SwiftData easily
        Creating this model version to store everything
     */

    var asSourceData: StorageDataObject {
        return StorageDataObject(self)
    }

    public struct StorageDataObject: Equatable, Hashable, Codable {
        public var id: String
        var version: String
        var createdAt: Date

        // Optional relationships
        var notificationV1: NotificationItem?
        var notificationV2: NotificationItemV2?

        enum CodingKeys: String, CodingKey {
            case id
            case version
            case createdAt
            case notificationV1
            case notificationV2
        }

        init(_ item: InAppNotificationItem) {
            self.version = item.storageVersionIdentifier

            switch item {
            case .v1(let item):
                self.notificationV1 = item
            case .v2(let item):
                self.notificationV2 = item
            }

            self.id = item.id
            self.createdAt = item.createdAt
        }

        mutating func updateFromNewValue(_ newItem: InAppNotificationItem) {
            self.version = newItem.storageVersionIdentifier

            switch newItem {
            case .v1(let item):
                self.notificationV1 = item
            case .v2(let item):
                self.notificationV2 = item
            }

            self.id = newItem.id
            self.createdAt = newItem.createdAt
        }

        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            id = try container.decode(String.self, forKey: .id)
            version = try container.decode(String.self, forKey: .version)
            createdAt = try container.decode(Date.self, forKey: .createdAt)
            notificationV1 = try container.decodeIfPresent(NotificationItem.self, forKey: .notificationV1)
            notificationV2 = try container.decodeIfPresent(NotificationItemV2.self, forKey: .notificationV2)
        }

        public func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(id, forKey: .id)
            try container.encode(version, forKey: .version)
            try container.encode(createdAt, forKey: .createdAt)
            try container.encodeIfPresent(notificationV1, forKey: .notificationV1)
            try container.encodeIfPresent(notificationV2, forKey: .notificationV2)
        }

        var asInAppNotificationItem: InAppNotificationItem? {
            if version == "v1" {
                guard let notification = notificationV1 else { return nil }
                return .v1(notification)

            } else if version == "v2" {
                guard let notification = notificationV2 else { return nil }
                return .v2(notification)

            } else {
                return nil
            }
        }
    }
}
