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

import XCTest
import CoreData
@testable import DailyNotificationPlugin

/**
 * Unit tests for NotificationConfigDAO
 * 
 * Tests CRUD operations and query helpers for configuration management
 */
class NotificationConfigDAOTests: XCTestCase {
    
    var persistenceController: PersistenceController!
    var context: NSManagedObjectContext!
    
    override func setUp() {
        super.setUp()
        
        // Create in-memory Core Data stack
        persistenceController = PersistenceController(inMemory: true)
        context = persistenceController.viewContext
        
        XCTAssertNotNil(context, "Context should be available")
    }
    
    override func tearDown() {
        context = nil
        persistenceController = nil
        super.tearDown()
    }
    
    // MARK: - Create/Insert Tests
    
    func testCreate_WithAllParameters() {
        // Given: All parameters
        let id = UUID().uuidString
        
        // When: Create entity
        let entity = NotificationConfig.create(
            in: context,
            id: id,
            timesafariDid: "test-did",
            configType: "notification",
            configKey: "sound_enabled",
            configValue: "true",
            configDataType: "bool",
            isEncrypted: false,
            encryptionKeyId: nil,
            ttlSeconds: 86400,
            isActive: true,
            metadata: "{\"key\":\"value\"}"
        )
        
        // Then: Entity should be created with correct values
        XCTAssertNotNil(entity, "Entity should be created")
        XCTAssertEqual(entity.id, id)
        XCTAssertEqual(entity.timesafariDid, "test-did")
        XCTAssertEqual(entity.configType, "notification")
        XCTAssertEqual(entity.configKey, "sound_enabled")
        XCTAssertEqual(entity.configValue, "true")
        XCTAssertEqual(entity.configDataType, "bool")
        XCTAssertEqual(entity.isEncrypted, false)
        XCTAssertEqual(entity.ttlSeconds, 86400)
        XCTAssertEqual(entity.isActive, true)
        XCTAssertNotNil(entity.createdAt)
        XCTAssertNotNil(entity.updatedAt)
    }
    
    func testCreate_WithMinimalParameters() {
        // Given: Minimal parameters (only required id)
        let id = UUID().uuidString
        
        // When: Create entity
        let entity = NotificationConfig.create(
            in: context,
            id: id
        )
        
        // Then: Entity should be created with defaults
        XCTAssertNotNil(entity, "Entity should be created")
        XCTAssertEqual(entity.id, id)
        XCTAssertEqual(entity.isEncrypted, false) // Default
        XCTAssertEqual(entity.ttlSeconds, 604800) // Default (7 days)
        XCTAssertEqual(entity.isActive, true) // Default
        XCTAssertNotNil(entity.createdAt)
        XCTAssertNotNil(entity.updatedAt)
    }
    
    func testCreate_FromDictionary_WithEpochMillis() {
        // Given: Dictionary with epoch milliseconds
        let createdAtMillis: Int64 = 1609459200000
        let dict: [String: Any] = [
            "id": "test-id",
            "configKey": "test_key",
            "configValue": "test_value",
            "createdAt": createdAtMillis,
            "isActive": true
        ]
        
        // When: Create from dictionary
        let entity = NotificationConfig.create(in: context, from: dict)
        
        // Then: Entity should be created with converted dates
        XCTAssertNotNil(entity, "Entity should be created")
        XCTAssertEqual(entity.id, "test-id")
        XCTAssertEqual(entity.configKey, "test_key")
        XCTAssertEqual(entity.configValue, "test_value")
        XCTAssertEqual(entity.isActive, true)
        
        // Verify date conversion
        let expectedDate = DailyNotificationDataConversions.dateFromEpochMillis(createdAtMillis)
        XCTAssertEqual(entity.createdAt, expectedDate)
    }
    
    func testCreate_FromDictionary_MissingRequiredId() {
        // Given: Dictionary without required id
        let dict: [String: Any] = [
            "configKey": "test_key"
        ]
        
        // When: Create from dictionary
        let entity = NotificationConfig.create(in: context, from: dict)
        
        // Then: Should be nil
        XCTAssertNil(entity, "Missing id should produce nil")
    }
    
    // MARK: - Read/Query Tests
    
    func testFetch_ById_Found() {
        // Given: Entity in database
        let id = UUID().uuidString
        let entity = NotificationConfig.create(
            in: context,
            id: id
        )
        try! context.save()
        
        // When: Fetch by id
        let fetched = NotificationConfig.fetch(by: id, in: context)
        
        // Then: Should find entity
        XCTAssertNotNil(fetched, "Should find entity")
        XCTAssertEqual(fetched?.id, id)
    }
    
    func testFetch_ById_NotFound() {
        // Given: No entity in database
        
        // When: Fetch by non-existent id
        let fetched = NotificationConfig.fetch(by: "non-existent", in: context)
        
        // Then: Should be nil
        XCTAssertNil(fetched, "Should not find entity")
    }
    
    func testFetch_ByConfigKey_Found() {
        // Given: Entity with configKey
        let configKey = "sound_enabled"
        let entity = NotificationConfig.create(
            in: context,
            id: UUID().uuidString,
            configKey: configKey
        )
        try! context.save()
        
        // When: Fetch by configKey
        let fetched = NotificationConfig.fetch(by: configKey, in: context)
        
        // Then: Should find entity
        XCTAssertNotNil(fetched, "Should find entity")
        XCTAssertEqual(fetched?.configKey, configKey)
    }
    
    func testFetch_ByConfigKey_NotFound() {
        // Given: No entity in database
        
        // When: Fetch by non-existent configKey
        let fetched = NotificationConfig.fetch(by: "non-existent", in: context)
        
        // Then: Should be nil
        XCTAssertNil(fetched, "Should not find entity")
    }
    
    func testFetchAll_Empty() {
        // Given: Empty database
        
        // When: Fetch all
        let all = NotificationConfig.fetchAll(in: context)
        
        // Then: Should be empty
        XCTAssertEqual(all.count, 0, "Should be empty")
    }
    
    func testFetchAll_WithEntities() {
        // Given: Multiple entities
        for i in 1...5 {
            NotificationConfig.create(
                in: context,
                id: "id-\(i)"
            )
        }
        try! context.save()
        
        // When: Fetch all
        let all = NotificationConfig.fetchAll(in: context)
        
        // Then: Should find all
        XCTAssertEqual(all.count, 5, "Should find all entities")
    }
    
    func testQuery_ByTimesafariDid() {
        // Given: Entities with different timesafariDid
        NotificationConfig.create(
            in: context,
            id: "id-1",
            timesafariDid: "did-1"
        )
        NotificationConfig.create(
            in: context,
            id: "id-2",
            timesafariDid: "did-1"
        )
        NotificationConfig.create(
            in: context,
            id: "id-3",
            timesafariDid: "did-2"
        )
        try! context.save()
        
        // When: Query by timesafariDid
        let results = NotificationConfig.query(by: "did-1", in: context)
        
        // Then: Should find only matching entities
        XCTAssertEqual(results.count, 2, "Should find 2 entities")
        XCTAssertTrue(results.allSatisfy { $0.timesafariDid == "did-1" })
    }
    
    func testQuery_ByConfigType() {
        // Given: Entities with different config types
        NotificationConfig.create(
            in: context,
            id: "id-1",
            configType: "notification"
        )
        NotificationConfig.create(
            in: context,
            id: "id-2",
            configType: "notification"
        )
        NotificationConfig.create(
            in: context,
            id: "id-3",
            configType: "scheduling"
        )
        try! context.save()
        
        // When: Query by configType
        let results = NotificationConfig.query(by: "notification", in: context)
        
        // Then: Should find only matching entities
        XCTAssertEqual(results.count, 2, "Should find 2 entities")
        XCTAssertTrue(results.allSatisfy { $0.configType == "notification" })
    }
    
    func testQueryActive() {
        // Given: Entities with different active states
        NotificationConfig.create(
            in: context,
            id: "id-1",
            isActive: true
        )
        NotificationConfig.create(
            in: context,
            id: "id-2",
            isActive: true
        )
        NotificationConfig.create(
            in: context,
            id: "id-3",
            isActive: false
        )
        try! context.save()
        
        // When: Query active
        let results = NotificationConfig.queryActive(in: context)
        
        // Then: Should find only active entities
        XCTAssertEqual(results.count, 2, "Should find 2 active entities")
        XCTAssertTrue(results.allSatisfy { $0.isActive == true })
    }
    
    func testQuery_ByConfigTypeAndIsActive() {
        // Given: Entities with different types and active states
        NotificationConfig.create(
            in: context,
            id: "id-1",
            configType: "notification",
            isActive: true
        )
        NotificationConfig.create(
            in: context,
            id: "id-2",
            configType: "notification",
            isActive: true
        )
        NotificationConfig.create(
            in: context,
            id: "id-3",
            configType: "notification",
            isActive: false
        )
        NotificationConfig.create(
            in: context,
            id: "id-4",
            configType: "scheduling",
            isActive: true
        )
        try! context.save()
        
        // When: Query by configType and isActive
        let results = NotificationConfig.query(
            by: "notification",
            isActive: true,
            in: context
        )
        
        // Then: Should find only matching entities
        XCTAssertEqual(results.count, 2, "Should find 2 entities")
        XCTAssertTrue(results.allSatisfy { 
            $0.configType == "notification" && $0.isActive == true 
        })
    }
    
    // MARK: - Update Tests
    
    func testUpdateValue() {
        // Given: Entity with initial value
        let entity = NotificationConfig.create(
            in: context,
            id: UUID().uuidString,
            configValue: "old_value"
        )
        let originalUpdatedAt = entity.updatedAt
        try! context.save()
        
        // Wait a bit to ensure time difference
        Thread.sleep(forTimeInterval: 0.1)
        
        // When: Update value
        entity.updateValue("new_value")
        try! context.save()
        
        // Then: Value and updatedAt should be updated
        XCTAssertEqual(entity.configValue, "new_value")
        XCTAssertNotNil(entity.updatedAt)
        XCTAssertGreaterThan(entity.updatedAt!, originalUpdatedAt!)
    }
    
    func testSetActive() {
        // Given: Entity with initial active state
        let entity = NotificationConfig.create(
            in: context,
            id: UUID().uuidString,
            isActive: true
        )
        try! context.save()
        
        // When: Set inactive
        entity.setActive(false)
        try! context.save()
        
        // Then: Active state should be updated
        XCTAssertEqual(entity.isActive, false)
    }
    
    func testTouch_UpdatesUpdatedAt() {
        // Given: Entity with original updatedAt
        let entity = NotificationConfig.create(
            in: context,
            id: UUID().uuidString
        )
        let originalUpdatedAt = entity.updatedAt
        try! context.save()
        
        // Wait a bit to ensure time difference
        Thread.sleep(forTimeInterval: 0.1)
        
        // When: Touch entity
        entity.touch()
        try! context.save()
        
        // Then: updatedAt should be newer
        XCTAssertNotNil(entity.updatedAt)
        XCTAssertGreaterThan(entity.updatedAt!, originalUpdatedAt!)
    }
    
    // MARK: - Delete Tests
    
    func testDelete_ById_Found() {
        // Given: Entity in database
        let id = UUID().uuidString
        NotificationConfig.create(
            in: context,
            id: id
        )
        try! context.save()
        
        // When: Delete by id
        let deleted = NotificationConfig.delete(by: id, in: context)
        
        // Then: Should be deleted
        XCTAssertTrue(deleted, "Should delete entity")
        
        // Verify deleted
        let fetched = NotificationConfig.fetch(by: id, in: context)
        XCTAssertNil(fetched, "Entity should be deleted")
    }
    
    func testDelete_ByConfigKey_Found() {
        // Given: Entity with configKey
        let configKey = "sound_enabled"
        NotificationConfig.create(
            in: context,
            id: UUID().uuidString,
            configKey: configKey
        )
        try! context.save()
        
        // When: Delete by configKey
        let deleted = NotificationConfig.delete(by: configKey, in: context)
        
        // Then: Should be deleted
        XCTAssertTrue(deleted, "Should delete entity")
        
        // Verify deleted
        let fetched = NotificationConfig.fetch(by: configKey, in: context)
        XCTAssertNil(fetched, "Entity should be deleted")
    }
    
    func testDeleteAll() {
        // Given: Multiple entities
        for i in 1...5 {
            NotificationConfig.create(
                in: context,
                id: "id-\(i)"
            )
        }
        try! context.save()
        
        // When: Delete all
        let count = NotificationConfig.deleteAll(in: context)
        
        // Then: Should delete all
        XCTAssertEqual(count, 5, "Should delete 5 entities")
        
        // Verify all deleted
        let all = NotificationConfig.fetchAll(in: context)
        XCTAssertEqual(all.count, 0, "Should be empty")
    }
}

