import Foundation
import SwiftData

public struct NotificationItemV2: Equatable, Identifiable, Hashable, Codable, Expirable {
    public var id: String
    /** The ID of the notification. */

    public var priority: Int
    /**
        The priority of the notification.
        0 is the highest priority, i.e. system announcement as banner.
        2 is the default priority for notifications that don't require CTAs.
     */

    public var updatedAt: Date
    /** The timestamp of the notification. */

    public var expiresAt: Date?
    /** The timestamp of the notification that determines the expiration of the notification. */

    public var isRead: Bool
    /** Whether the notification has been read. */

    public var notificationType: NotificationType {
        return NotificationType(rawValue: notificationTypeValue) ?? .restrictedByDefault
    }

    /** The type of the notification. */

    public var notificationTypeValue: String

    public var userProfiles: [MiniProfileV2]
    /** The profiles of the users that are mentioned in the notification. */

    public var totalUsers: Int
    /**
        The total number of users that are mentioned in the notification.
        default to -1 to indicate that the value is not available.
     */

    public var contentId: String?
    /** The ID of the content that is mentioned in the notification. */

    public var contentAncillaryId: String?
    /** The ancillary ID of the content (e.g., comment ID for comment mentions). */

    public var contentTitle: String?
    /** The title of the content that is mentioned in the notification. */

    public var contentImageUrl: String?
    /** The image URL of the content that is mentioned in the notification. */

    public var contentMessage: String?
    /** The message of the content that is mentioned in the notification. */

    public var redirectUrl: String?
    /** The URL to redirect to when the notification is clicked. */

    init(_ remote: Components.Schemas.NotificationItemV2) throws {
        self.id = remote.id
        self.priority = remote.priority ?? .zero
        self.updatedAt = remote.updated_at ?? .now
        self.expiresAt = remote.expires_at
        self.isRead = remote.is_read ?? false
        self.notificationTypeValue = remote.notification_type ?? ""
        let userProfiles = remote.user_profiles ?? []
        self.userProfiles = userProfiles.compactMap { try? MiniProfileV2($0) }
        self.totalUsers = remote.total_users ?? .zero
        self.contentId = remote.content_id
        self.contentAncillaryId = remote.content_ancillary_id
        self.contentTitle = remote.content_title
        self.contentImageUrl = remote.content_image_url
        self.contentMessage = remote.content_message
        self.redirectUrl = remote.redirect_url
    }

    enum CodingKeys: String, CodingKey {
        case id
        case priority
        case updatedAt = "updated_at"
        case expiresAt = "expires_at"
        case isRead = "is_read"
        case notificationTypeValue = "notification_type_value"
        case userProfiles = "user_profiles"
        case totalUsers = "total_users"
        case contentId = "content_id"
        case contentAncillaryId = "content_ancillary_id"
        case contentTitle = "content_title"
        case contentImageUrl = "content_image_url"
        case contentMessage = "content_message"
        case redirectUrl = "redirect_url"
    }

    // Custom decoder to handle the `notificationType` computed property
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        priority = try container.decode(Int.self, forKey: .priority)
        updatedAt = try container.decode(Date.self, forKey: .updatedAt)
        expiresAt = try container.decodeIfPresent(Date.self, forKey: .expiresAt)
        isRead = try container.decode(Bool.self, forKey: .isRead)
        notificationTypeValue = try container.decode(String.self, forKey: .notificationTypeValue)
        userProfiles = try container.decode([MiniProfileV2].self, forKey: .userProfiles)
        totalUsers = try container.decode(Int.self, forKey: .totalUsers)
        contentId = try container.decodeIfPresent(String.self, forKey: .contentId)
        contentAncillaryId = try container.decodeIfPresent(String.self, forKey: .contentAncillaryId)
        contentTitle = try container.decodeIfPresent(String.self, forKey: .contentTitle)
        contentImageUrl = try container.decodeIfPresent(String.self, forKey: .contentImageUrl)
        contentMessage = try container.decodeIfPresent(String.self, forKey: .contentMessage)
        redirectUrl = try container.decodeIfPresent(String.self, forKey: .redirectUrl)
    }

    // Custom encoder to handle the `notificationType` computed property
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(priority, forKey: .priority)
        try container.encode(updatedAt, forKey: .updatedAt)
        try container.encode(expiresAt, forKey: .expiresAt)
        try container.encode(isRead, forKey: .isRead)
        try container.encode(notificationTypeValue, forKey: .notificationTypeValue)
        try container.encode(userProfiles, forKey: .userProfiles)
        try container.encode(totalUsers, forKey: .totalUsers)
        try container.encode(contentId, forKey: .contentId)
        try container.encode(contentAncillaryId, forKey: .contentAncillaryId)
        try container.encode(contentTitle, forKey: .contentTitle)
        try container.encode(contentImageUrl, forKey: .contentImageUrl)
        try container.encode(contentMessage, forKey: .contentMessage)
        try container.encode(redirectUrl, forKey: .redirectUrl)
    }
}

/* Convenience Computed Values */
public extension NotificationItemV2 {
    var userProfileURLs: [String?] {
        return userProfiles.map { $0.avatarImageURL }
    }

    var isExpired: Bool {
        // Use expires at date or defaults to 60 days old if expires at date is not available.
        let expiryDate = expiresAt ?? Calendar.current.date(byAdding: .day, value: 60, to: updatedAt)!
        return Date() > expiryDate
    }
}
