/**
 * NotificationContent.swift
 * 
 * Data structure for notification content
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

import Foundation

/**
 * Data structure representing notification content
 * 
 * This class encapsulates all the information needed for a notification
 * including scheduling, content, and metadata.
 */
public class NotificationContent: Codable {
    
    // MARK: - Properties
    
    public let id: String
    public let title: String?
    public let body: String?
    public let scheduledTime: Int64 // milliseconds since epoch (matches Android long)
    public let fetchedAt: Int64 // milliseconds since epoch (matches Android long)
    public let url: String?
    public let payload: [String: Any]?
    public let etag: String?
    /** When > 0, next occurrence is this many minutes after trigger (dev/testing). Nil/0 = 24h. Persisted for rollover and recovery. */
    var rolloverIntervalMinutes: Int?
    
    // Phase 2: Delivery tracking properties
    var deliveryStatus: String? // e.g., "scheduled", "delivered", "missed", "error"
    var lastDeliveryAttempt: Int64? // milliseconds since epoch (matches Android long)
    
    // MARK: - Codable Support
    
    enum CodingKeys: String, CodingKey {
        case id
        case title
        case body
        case scheduledTime
        case fetchedAt
        case url
        case payload
        case etag
        case rolloverIntervalMinutes
        case deliveryStatus
        case lastDeliveryAttempt
    }
    
    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        title = try container.decodeIfPresent(String.self, forKey: .title)
        body = try container.decodeIfPresent(String.self, forKey: .body)
        scheduledTime = try container.decode(Int64.self, forKey: .scheduledTime)
        fetchedAt = try container.decode(Int64.self, forKey: .fetchedAt)
        url = try container.decodeIfPresent(String.self, forKey: .url)
        // payload is encoded as JSON string
        if let payloadString = try? container.decodeIfPresent(String.self, forKey: .payload),
           let payloadData = payloadString.data(using: .utf8),
           let payloadDict = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any] {
            payload = payloadDict
        } else {
            payload = nil
        }
        etag = try container.decodeIfPresent(String.self, forKey: .etag)
        rolloverIntervalMinutes = try container.decodeIfPresent(Int.self, forKey: .rolloverIntervalMinutes)
        deliveryStatus = try container.decodeIfPresent(String.self, forKey: .deliveryStatus)
        lastDeliveryAttempt = try container.decodeIfPresent(Int64.self, forKey: .lastDeliveryAttempt)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encodeIfPresent(title, forKey: .title)
        try container.encodeIfPresent(body, forKey: .body)
        try container.encode(scheduledTime, forKey: .scheduledTime)
        try container.encode(fetchedAt, forKey: .fetchedAt)
        try container.encodeIfPresent(url, forKey: .url)
        // Encode payload as JSON string
        if let payload = payload,
           let payloadData = try? JSONSerialization.data(withJSONObject: payload),
           let payloadString = String(data: payloadData, encoding: .utf8) {
            try container.encode(payloadString, forKey: .payload)
        }
        try container.encodeIfPresent(etag, forKey: .etag)
        try container.encodeIfPresent(rolloverIntervalMinutes, forKey: .rolloverIntervalMinutes)
        try container.encodeIfPresent(deliveryStatus, forKey: .deliveryStatus)
        try container.encodeIfPresent(lastDeliveryAttempt, forKey: .lastDeliveryAttempt)
    }
    
    // MARK: - Initialization
    
    /**
     * Initialize notification content
     * 
     * @param id Unique notification identifier
     * @param title Notification title
     * @param body Notification body text
     * @param scheduledTime When notification should fire (milliseconds since epoch)
     * @param fetchedAt When content was fetched (milliseconds since epoch)
     * @param url URL for content fetching
     * @param payload Additional payload data
     * @param etag ETag for HTTP caching
     * @param rolloverIntervalMinutes When > 0, next occurrence is this many minutes after trigger (dev/testing). Nil/0 = 24h.
     * @param deliveryStatus Delivery status (optional, Phase 2)
     * @param lastDeliveryAttempt Last delivery attempt timestamp (optional, Phase 2)
     */
    public init(id: String,
         title: String?,
         body: String?,
         scheduledTime: Int64,
         fetchedAt: Int64,
         url: String?,
         payload: [String: Any]?,
         etag: String?,
         rolloverIntervalMinutes: Int? = nil,
         deliveryStatus: String? = nil,
         lastDeliveryAttempt: Int64? = nil) {
        self.id = id
        self.title = title
        self.body = body
        self.scheduledTime = scheduledTime
        self.fetchedAt = fetchedAt
        self.url = url
        self.payload = payload
        self.etag = etag
        self.rolloverIntervalMinutes = rolloverIntervalMinutes
        self.deliveryStatus = deliveryStatus
        self.lastDeliveryAttempt = lastDeliveryAttempt
    }
    
    // MARK: - Convenience Methods
    
    /**
     * Get scheduled time as Date
     * 
     * @return Scheduled time as Date object
     */
    func getScheduledTimeAsDate() -> Date {
        return Date(timeIntervalSince1970: Double(scheduledTime) / 1000.0)
    }
    
    /**
     * Get fetched time as Date
     * 
     * @return Fetched time as Date object
     */
    func getFetchedTimeAsDate() -> Date {
        return Date(timeIntervalSince1970: Double(fetchedAt) / 1000.0)
    }
    
    /**
     * Check if notification is scheduled for today
     * 
     * @return true if scheduled for today
     */
    func isScheduledForToday() -> Bool {
        let scheduledDate = getScheduledTimeAsDate()
        let today = Date()
        
        let calendar = Calendar.current
        return calendar.isDate(scheduledDate, inSameDayAs: today)
    }
    
    /**
     * Check if notification is scheduled for tomorrow
     * 
     * @return true if scheduled for tomorrow
     */
    func isScheduledForTomorrow() -> Bool {
        let scheduledDate = getScheduledTimeAsDate()
        let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date()
        
        let calendar = Calendar.current
        return calendar.isDate(scheduledDate, inSameDayAs: tomorrow)
    }
    
    /**
     * Check if notification is in the future
     * 
     * @return true if scheduled time is in the future
     */
    func isInTheFuture() -> Bool {
        let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
        return scheduledTime > currentTime
    }
    
    /**
     * Get age of content at scheduled time
     * 
     * @return Age in seconds at scheduled time
     */
    func getAgeAtScheduledTime() -> TimeInterval {
        return Double(scheduledTime - fetchedAt) / 1000.0
    }
    
    /**
     * Convert to dictionary representation
     * 
     * @return Dictionary representation of notification content
     */
    func toDictionary() -> [String: Any] {
        var dict: [String: Any] = [
            "id": id,
            "title": title ?? "",
            "body": body ?? "",
            "scheduledTime": scheduledTime,
            "fetchedAt": fetchedAt,
            "url": url ?? "",
            "payload": payload ?? [:],
            "etag": etag ?? ""
        ]
        
        // Phase 2: Add delivery tracking properties if present
        if let deliveryStatus = deliveryStatus {
            dict["deliveryStatus"] = deliveryStatus
        }
        if let lastDeliveryAttempt = lastDeliveryAttempt {
            dict["lastDeliveryAttempt"] = lastDeliveryAttempt
        }
        
        return dict
    }
    
    /**
     * Create from dictionary representation
     * 
     * @param dict Dictionary representation
     * @return NotificationContent instance
     */
    static func fromDictionary(_ dict: [String: Any]) -> NotificationContent? {
        guard let id = dict["id"] as? String else {
            return nil
        }
        
        // Handle both Int64 and TimeInterval (Double) for backward compatibility
        let scheduledTime: Int64
        if let time = dict["scheduledTime"] as? Int64 {
            scheduledTime = time
        } else if let time = dict["scheduledTime"] as? Double {
            scheduledTime = Int64(time)
        } else {
            return nil
        }
        
        let fetchedAt: Int64
        if let time = dict["fetchedAt"] as? Int64 {
            fetchedAt = time
        } else if let time = dict["fetchedAt"] as? Double {
            fetchedAt = Int64(time)
        } else {
            return nil
        }
        
        // Handle lastDeliveryAttempt (can be Int64 or Double/TimeInterval)
        let lastDeliveryAttempt: Int64?
        if let attempt = dict["lastDeliveryAttempt"] as? Int64 {
            lastDeliveryAttempt = attempt
        } else if let attempt = dict["lastDeliveryAttempt"] as? Double {
            lastDeliveryAttempt = Int64(attempt)
        } else {
            lastDeliveryAttempt = nil
        }
        
        let rollover = (dict["rolloverIntervalMinutes"] as? NSNumber)?.intValue
        return NotificationContent(
            id: id,
            title: dict["title"] as? String,
            body: dict["body"] as? String,
            scheduledTime: scheduledTime,
            fetchedAt: fetchedAt,
            url: dict["url"] as? String,
            payload: dict["payload"] as? [String: Any],
            etag: dict["etag"] as? String,
            rolloverIntervalMinutes: rollover,
            deliveryStatus: dict["deliveryStatus"] as? String,
            lastDeliveryAttempt: lastDeliveryAttempt
        )
    }
}
