//
//  DailyNotificationDataConversionsTests.swift
//  DailyNotificationPluginTests
//
//  Created by Matthew Raymer on 2025-12-08
//  Copyright © 2025 TimeSafari. All rights reserved.
//

import XCTest
@testable import DailyNotificationPlugin

/**
 * Unit tests for DailyNotificationDataConversions
 * 
 * Tests all data type conversion helpers:
 * - Time conversions (Date ↔ Int64)
 * - Numeric conversions (Int ↔ Int32, Int64 ↔ Int32)
 * - String conversions (optional handling, JSON)
 */
class DailyNotificationDataConversionsTests: XCTestCase {
    
    // MARK: - Time Conversion Tests
    
    func testDateFromEpochMillis() {
        // Given: Epoch milliseconds
        let epochMillis: Int64 = 1609459200000 // 2021-01-01 00:00:00 UTC
        
        // When: Convert to Date
        let date = DailyNotificationDataConversions.dateFromEpochMillis(epochMillis)
        
        // Then: Should match expected date
        let expectedDate = Date(timeIntervalSince1970: 1609459200.0)
        XCTAssertEqual(date.timeIntervalSince1970, expectedDate.timeIntervalSince1970, 
                       accuracy: 0.001, "Date conversion should be accurate")
    }
    
    func testEpochMillisFromDate() {
        // Given: Date
        let date = Date(timeIntervalSince1970: 1609459200.0) // 2021-01-01 00:00:00 UTC
        
        // When: Convert to epoch milliseconds
        let epochMillis = DailyNotificationDataConversions.epochMillisFromDate(date)
        
        // Then: Should match expected milliseconds
        XCTAssertEqual(epochMillis, 1609459200000, "Epoch milliseconds should match")
    }
    
    func testDateFromEpochMillis_RoundTrip() {
        // Given: Original epoch milliseconds
        let originalMillis: Int64 = 1609459200000
        
        // When: Convert to Date and back
        let date = DailyNotificationDataConversions.dateFromEpochMillis(originalMillis)
        let convertedMillis = DailyNotificationDataConversions.epochMillisFromDate(date)
        
        // Then: Should match original
        XCTAssertEqual(convertedMillis, originalMillis, "Round trip conversion should preserve value")
    }
    
    func testDateFromEpochMillis_Optional_Nil() {
        // Given: Nil optional
        let optionalMillis: Int64? = nil
        
        // When: Convert to optional Date
        let date = DailyNotificationDataConversions.dateFromEpochMillis(optionalMillis)
        
        // Then: Should be nil
        XCTAssertNil(date, "Nil input should produce nil output")
    }
    
    func testDateFromEpochMillis_Optional_Value() {
        // Given: Optional with value
        let optionalMillis: Int64? = 1609459200000
        
        // When: Convert to optional Date
        let date = DailyNotificationDataConversions.dateFromEpochMillis(optionalMillis)
        
        // Then: Should have value
        XCTAssertNotNil(date, "Non-nil input should produce non-nil output")
        XCTAssertEqual(date!.timeIntervalSince1970, 1609459200.0, accuracy: 0.001)
    }
    
    func testEpochMillisFromDate_Optional_Nil() {
        // Given: Nil optional Date
        let optionalDate: Date? = nil
        
        // When: Convert to optional milliseconds
        let millis = DailyNotificationDataConversions.epochMillisFromDate(optionalDate)
        
        // Then: Should be nil
        XCTAssertNil(millis, "Nil input should produce nil output")
    }
    
    func testEpochMillisFromDate_Optional_Value() {
        // Given: Optional Date with value
        let optionalDate: Date? = Date(timeIntervalSince1970: 1609459200.0)
        
        // When: Convert to optional milliseconds
        let millis = DailyNotificationDataConversions.epochMillisFromDate(optionalDate)
        
        // Then: Should have value
        XCTAssertNotNil(millis, "Non-nil input should produce non-nil output")
        XCTAssertEqual(millis, 1609459200000)
    }
    
    // MARK: - Numeric Conversion Tests
    
    func testInt32FromInt() {
        // Given: Int value
        let intValue = 42
        
        // When: Convert to Int32
        let int32Value = DailyNotificationDataConversions.int32FromInt(intValue)
        
        // Then: Should match
        XCTAssertEqual(int32Value, 42, "Int to Int32 conversion should preserve value")
    }
    
    func testIntFromInt32() {
        // Given: Int32 value
        let int32Value: Int32 = 42
        
        // When: Convert to Int
        let intValue = DailyNotificationDataConversions.intFromInt32(int32Value)
        
        // Then: Should match
        XCTAssertEqual(intValue, 42, "Int32 to Int conversion should preserve value")
    }
    
    func testInt32FromInt64_WithinRange() {
        // Given: Int64 value within Int32 range
        let int64Value: Int64 = 2147483647 // Int32.max
        
        // When: Convert to Int32
        let int32Value = DailyNotificationDataConversions.int32FromInt64(int64Value)
        
        // Then: Should match
        XCTAssertEqual(int32Value, Int32.max, "Int64 to Int32 conversion should work within range")
    }
    
    func testInt32FromInt64_AboveMax() {
        // Given: Int64 value above Int32.max
        let int64Value: Int64 = 2147483648 // Int32.max + 1
        
        // When: Convert to Int32
        let int32Value = DailyNotificationDataConversions.int32FromInt64(int64Value)
        
        // Then: Should be clamped to Int32.max
        XCTAssertEqual(int32Value, Int32.max, "Int64 above max should be clamped")
    }
    
    func testInt32FromInt64_BelowMin() {
        // Given: Int64 value below Int32.min
        let int64Value: Int64 = -2147483649 // Int32.min - 1
        
        // When: Convert to Int32
        let int32Value = DailyNotificationDataConversions.int32FromInt64(int64Value)
        
        // Then: Should be clamped to Int32.min
        XCTAssertEqual(int32Value, Int32.min, "Int64 below min should be clamped")
    }
    
    func testInt64FromInt32() {
        // Given: Int32 value
        let int32Value: Int32 = 42
        
        // When: Convert to Int64
        let int64Value = DailyNotificationDataConversions.int64FromInt32(int32Value)
        
        // Then: Should match
        XCTAssertEqual(int64Value, 42, "Int32 to Int64 conversion should preserve value")
    }
    
    func testInt64FromLong() {
        // Given: Int64 value (Long)
        let longValue: Int64 = 42
        
        // When: Convert to Int64 (no-op)
        let int64Value = DailyNotificationDataConversions.int64FromLong(longValue)
        
        // Then: Should match
        XCTAssertEqual(int64Value, 42, "Long to Int64 conversion should preserve value")
    }
    
    func testBoolFromBoolean() {
        // Given: Bool value
        let boolValue = true
        
        // When: Convert to Bool (no-op)
        let convertedBool = DailyNotificationDataConversions.boolFromBoolean(boolValue)
        
        // Then: Should match
        XCTAssertEqual(convertedBool, true, "Boolean to Bool conversion should preserve value")
    }
    
    // MARK: - String Conversion Tests
    
    func testStringFromOptional_Nil() {
        // Given: Nil optional String
        let optionalString: String? = nil
        
        // When: Convert to String
        let string = DailyNotificationDataConversions.stringFromOptional(optionalString)
        
        // Then: Should be empty string
        XCTAssertEqual(string, "", "Nil optional should produce empty string")
    }
    
    func testStringFromOptional_Value() {
        // Given: Optional String with value
        let optionalString: String? = "test"
        
        // When: Convert to String
        let string = DailyNotificationDataConversions.stringFromOptional(optionalString)
        
        // Then: Should match
        XCTAssertEqual(string, "test", "Non-nil optional should produce value")
    }
    
    func testOptionalStringFromString_Empty() {
        // Given: Empty String
        let string = ""
        
        // When: Convert to optional String
        let optionalString = DailyNotificationDataConversions.optionalStringFromString(string)
        
        // Then: Should be nil
        XCTAssertNil(optionalString, "Empty string should produce nil")
    }
    
    func testOptionalStringFromString_Value() {
        // Given: Non-empty String
        let string = "test"
        
        // When: Convert to optional String
        let optionalString = DailyNotificationDataConversions.optionalStringFromString(string)
        
        // Then: Should have value
        XCTAssertEqual(optionalString, "test", "Non-empty string should produce value")
    }
    
    // MARK: - JSON Conversion Tests
    
    func testJsonStringFromDictionary_Valid() {
        // Given: Valid dictionary
        let dict: [String: Any] = ["key1": "value1", "key2": 42]
        
        // When: Convert to JSON string
        let jsonString = DailyNotificationDataConversions.jsonStringFromDictionary(dict)
        
        // Then: Should be valid JSON
        XCTAssertNotNil(jsonString, "Valid dictionary should produce JSON string")
        
        // Verify can be parsed back
        let parsedDict = DailyNotificationDataConversions.dictionaryFromJsonString(jsonString)
        XCTAssertNotNil(parsedDict, "JSON string should be parseable")
        XCTAssertEqual(parsedDict?["key1"] as? String, "value1")
        XCTAssertEqual(parsedDict?["key2"] as? Int, 42)
    }
    
    func testJsonStringFromDictionary_Nil() {
        // Given: Nil dictionary
        let dict: [String: Any]? = nil
        
        // When: Convert to JSON string
        let jsonString = DailyNotificationDataConversions.jsonStringFromDictionary(dict)
        
        // Then: Should be nil
        XCTAssertNil(jsonString, "Nil dictionary should produce nil")
    }
    
    func testDictionaryFromJsonString_Valid() {
        // Given: Valid JSON string
        let jsonString = "{\"key1\":\"value1\",\"key2\":42}"
        
        // When: Convert to dictionary
        let dict = DailyNotificationDataConversions.dictionaryFromJsonString(jsonString)
        
        // Then: Should be valid dictionary
        XCTAssertNotNil(dict, "Valid JSON string should produce dictionary")
        XCTAssertEqual(dict?["key1"] as? String, "value1")
        XCTAssertEqual(dict?["key2"] as? Int, 42)
    }
    
    func testDictionaryFromJsonString_Invalid() {
        // Given: Invalid JSON string
        let jsonString = "{invalid json}"
        
        // When: Convert to dictionary
        let dict = DailyNotificationDataConversions.dictionaryFromJsonString(jsonString)
        
        // Then: Should be nil
        XCTAssertNil(dict, "Invalid JSON string should produce nil")
    }
    
    func testDictionaryFromJsonString_Nil() {
        // Given: Nil JSON string
        let jsonString: String? = nil
        
        // When: Convert to dictionary
        let dict = DailyNotificationDataConversions.dictionaryFromJsonString(jsonString)
        
        // Then: Should be nil
        XCTAssertNil(dict, "Nil JSON string should produce nil")
    }
    
    func testJsonStringFromDictionary_RoundTrip() {
        // Given: Original dictionary
        let originalDict: [String: Any] = [
            "string": "value",
            "number": 42,
            "bool": true,
            "nested": ["key": "value"]
        ]
        
        // When: Convert to JSON and back
        let jsonString = DailyNotificationDataConversions.jsonStringFromDictionary(originalDict)
        let parsedDict = DailyNotificationDataConversions.dictionaryFromJsonString(jsonString)
        
        // Then: Should match original (with type conversions)
        XCTAssertNotNil(parsedDict, "Round trip should produce dictionary")
        XCTAssertEqual(parsedDict?["string"] as? String, "value")
        XCTAssertEqual(parsedDict?["number"] as? Int, 42)
        XCTAssertEqual(parsedDict?["bool"] as? Bool, true)
    }
}

