//
//  NotificationContentDAOTests.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 NotificationContentDAO
 * 
 * Tests CRUD operations, query helpers, and data conversions
 */
class NotificationContentDAOTests: 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 scheduledTime = Date()
        let id = UUID().uuidString
        
        // When: Create entity
        let entity = NotificationContent.create(
            in: context,
            id: id,
            pluginVersion: "1.0.0",
            timesafariDid: "test-did",
            notificationType: "daily",
            title: "Test Title",
            body: "Test Body",
            scheduledTime: scheduledTime,
            timezone: "UTC",
            priority: 5,
            vibrationEnabled: true,
            soundEnabled: true,
            mediaUrl: "https://example.com/media.jpg",
            encryptedContent: "encrypted",
            encryptionKeyId: "key-1",
            ttlSeconds: 86400,
            deliveryStatus: "scheduled",
            deliveryAttempts: 0,
            metadata: "{\"key\":\"value\"}"
        )
        
        // Then: Entity should be created with correct values
        XCTAssertNotNil(entity, "Entity should be created")
        XCTAssertEqual(entity.id, id)
        XCTAssertEqual(entity.pluginVersion, "1.0.0")
        XCTAssertEqual(entity.timesafariDid, "test-did")
        XCTAssertEqual(entity.notificationType, "daily")
        XCTAssertEqual(entity.title, "Test Title")
        XCTAssertEqual(entity.body, "Test Body")
        XCTAssertEqual(entity.scheduledTime, scheduledTime)
        XCTAssertEqual(entity.timezone, "UTC")
        XCTAssertEqual(entity.priority, 5)
        XCTAssertEqual(entity.vibrationEnabled, true)
        XCTAssertEqual(entity.soundEnabled, true)
        XCTAssertEqual(entity.mediaUrl, "https://example.com/media.jpg")
        XCTAssertEqual(entity.encryptedContent, "encrypted")
        XCTAssertEqual(entity.encryptionKeyId, "key-1")
        XCTAssertEqual(entity.ttlSeconds, 86400)
        XCTAssertEqual(entity.deliveryStatus, "scheduled")
        XCTAssertEqual(entity.deliveryAttempts, 0)
        XCTAssertNotNil(entity.createdAt)
        XCTAssertNotNil(entity.updatedAt)
    }
    
    func testCreate_WithMinimalParameters() {
        // Given: Minimal parameters (only required)
        let scheduledTime = Date()
        let id = UUID().uuidString
        
        // When: Create entity
        let entity = NotificationContent.create(
            in: context,
            id: id,
            scheduledTime: scheduledTime
        )
        
        // Then: Entity should be created with defaults
        XCTAssertNotNil(entity, "Entity should be created")
        XCTAssertEqual(entity.id, id)
        XCTAssertEqual(entity.scheduledTime, scheduledTime)
        XCTAssertEqual(entity.priority, 0) // Default
        XCTAssertEqual(entity.vibrationEnabled, false) // Default
        XCTAssertEqual(entity.soundEnabled, true) // Default
        XCTAssertEqual(entity.ttlSeconds, 604800) // Default (7 days)
        XCTAssertNotNil(entity.createdAt)
        XCTAssertNotNil(entity.updatedAt)
    }
    
    func testCreate_FromDictionary_WithEpochMillis() {
        // Given: Dictionary with epoch milliseconds
        let scheduledTimeMillis: Int64 = 1609459200000
        let createdAtMillis: Int64 = 1609459200000
        let dict: [String: Any] = [
            "id": "test-id",
            "title": "Test",
            "scheduledTime": scheduledTimeMillis,
            "createdAt": createdAtMillis,
            "priority": 5,
            "deliveryAttempts": 2
        ]
        
        // When: Create from dictionary
        let entity = NotificationContent.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.title, "Test")
        XCTAssertEqual(entity.priority, 5)
        XCTAssertEqual(entity.deliveryAttempts, 2)
        
        // Verify date conversion
        let expectedDate = DailyNotificationDataConversions.dateFromEpochMillis(scheduledTimeMillis)
        XCTAssertEqual(entity.scheduledTime, expectedDate)
    }
    
    func testCreate_FromDictionary_WithDate() {
        // Given: Dictionary with Date objects
        let scheduledTime = Date()
        let dict: [String: Any] = [
            "id": "test-id",
            "scheduledTime": scheduledTime,
            "title": "Test"
        ]
        
        // When: Create from dictionary
        let entity = NotificationContent.create(in: context, from: dict)
        
        // Then: Entity should be created
        XCTAssertNotNil(entity, "Entity should be created")
        XCTAssertEqual(entity.id, "test-id")
        XCTAssertEqual(entity.scheduledTime, scheduledTime)
    }
    
    func testCreate_FromDictionary_MissingRequiredId() {
        // Given: Dictionary without required id
        let dict: [String: Any] = [
            "title": "Test"
        ]
        
        // When: Create from dictionary
        let entity = NotificationContent.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 = NotificationContent.create(
            in: context,
            id: id,
            scheduledTime: Date()
        )
        try! context.save()
        
        // When: Fetch by id
        let fetched = NotificationContent.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
        // (empty context)
        
        // When: Fetch by non-existent id
        let fetched = NotificationContent.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 = NotificationContent.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 {
            NotificationContent.create(
                in: context,
                id: "id-\(i)",
                scheduledTime: Date()
            )
        }
        try! context.save()
        
        // When: Fetch all
        let all = NotificationContent.fetchAll(in: context)
        
        // Then: Should find all
        XCTAssertEqual(all.count, 5, "Should find all entities")
    }
    
    func testQuery_ByTimesafariDid() {
        // Given: Entities with different timesafariDid
        NotificationContent.create(
            in: context,
            id: "id-1",
            timesafariDid: "did-1",
            scheduledTime: Date()
        )
        NotificationContent.create(
            in: context,
            id: "id-2",
            timesafariDid: "did-1",
            scheduledTime: Date()
        )
        NotificationContent.create(
            in: context,
            id: "id-3",
            timesafariDid: "did-2",
            scheduledTime: Date()
        )
        try! context.save()
        
        // When: Query by timesafariDid
        let results = NotificationContent.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_ByNotificationType() {
        // Given: Entities with different notification types
        NotificationContent.create(
            in: context,
            id: "id-1",
            notificationType: "daily",
            scheduledTime: Date()
        )
        NotificationContent.create(
            in: context,
            id: "id-2",
            notificationType: "daily",
            scheduledTime: Date()
        )
        NotificationContent.create(
            in: context,
            id: "id-3",
            notificationType: "weekly",
            scheduledTime: Date()
        )
        try! context.save()
        
        // When: Query by notificationType
        let results = NotificationContent.query(by: "daily", in: context)
        
        // Then: Should find only matching entities
        XCTAssertEqual(results.count, 2, "Should find 2 entities")
        XCTAssertTrue(results.allSatisfy { $0.notificationType == "daily" })
    }
    
    func testQuery_ScheduledTimeBetween() {
        // Given: Entities with different scheduled times
        let startDate = Date()
        let midDate = startDate.addingTimeInterval(3600) // 1 hour later
        let endDate = startDate.addingTimeInterval(7200) // 2 hours later
        let outsideDate = startDate.addingTimeInterval(10800) // 3 hours later
        
        NotificationContent.create(in: context, id: "id-1", scheduledTime: startDate)
        NotificationContent.create(in: context, id: "id-2", scheduledTime: midDate)
        NotificationContent.create(in: context, id: "id-3", scheduledTime: endDate)
        NotificationContent.create(in: context, id: "id-4", scheduledTime: outsideDate)
        try! context.save()
        
        // When: Query by scheduledTime range
        let results = NotificationContent.query(
            scheduledTimeBetween: startDate,
            and: endDate,
            in: context
        )
        
        // Then: Should find entities in range
        XCTAssertEqual(results.count, 3, "Should find 3 entities in range")
        XCTAssertTrue(results.allSatisfy { 
            $0.scheduledTime! >= startDate && $0.scheduledTime! <= endDate 
        })
    }
    
    func testQuery_ByDeliveryStatus() {
        // Given: Entities with different delivery statuses
        NotificationContent.create(
            in: context,
            id: "id-1",
            deliveryStatus: "scheduled",
            scheduledTime: Date()
        )
        NotificationContent.create(
            in: context,
            id: "id-2",
            deliveryStatus: "scheduled",
            scheduledTime: Date()
        )
        NotificationContent.create(
            in: context,
            id: "id-3",
            deliveryStatus: "delivered",
            scheduledTime: Date()
        )
        try! context.save()
        
        // When: Query by deliveryStatus
        let results = NotificationContent.query(by: "scheduled", in: context)
        
        // Then: Should find only matching entities
        XCTAssertEqual(results.count, 2, "Should find 2 entities")
        XCTAssertTrue(results.allSatisfy { $0.deliveryStatus == "scheduled" })
    }
    
    func testQueryReadyForDelivery() {
        // Given: Entities with different scheduled times
        let now = Date()
        let past = now.addingTimeInterval(-3600) // 1 hour ago
        let future = now.addingTimeInterval(3600) // 1 hour from now
        
        NotificationContent.create(in: context, id: "id-1", scheduledTime: past)
        NotificationContent.create(in: context, id: "id-2", scheduledTime: now)
        NotificationContent.create(in: context, id: "id-3", scheduledTime: future)
        try! context.save()
        
        // When: Query ready for delivery
        let results = NotificationContent.queryReadyForDelivery(currentTime: now, in: context)
        
        // Then: Should find only past/current entities
        XCTAssertEqual(results.count, 2, "Should find 2 ready entities")
        XCTAssertTrue(results.allSatisfy { $0.scheduledTime! <= now })
    }
    
    // MARK: - Update Tests
    
    func testTouch_UpdatesUpdatedAt() {
        // Given: Entity with original updatedAt
        let entity = NotificationContent.create(
            in: context,
            id: "test-id",
            scheduledTime: Date()
        )
        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!)
    }
    
    func testUpdateDeliveryStatus() {
        // Given: Entity with initial status
        let entity = NotificationContent.create(
            in: context,
            id: "test-id",
            deliveryStatus: "scheduled",
            deliveryAttempts: 0,
            scheduledTime: Date()
        )
        try! context.save()
        
        // When: Update delivery status
        entity.updateDeliveryStatus("delivered")
        try! context.save()
        
        // Then: Status and attempts should be updated
        XCTAssertEqual(entity.deliveryStatus, "delivered")
        XCTAssertEqual(entity.deliveryAttempts, 1)
        XCTAssertNotNil(entity.lastDeliveryAttempt)
    }
    
    func testRecordUserInteraction() {
        // Given: Entity with no interactions
        let entity = NotificationContent.create(
            in: context,
            id: "test-id",
            userInteractionCount: 0,
            scheduledTime: Date()
        )
        try! context.save()
        
        // When: Record user interaction
        entity.recordUserInteraction()
        try! context.save()
        
        // Then: Interaction count should increase
        XCTAssertEqual(entity.userInteractionCount, 1)
        XCTAssertNotNil(entity.lastUserInteraction)
    }
    
    // MARK: - Delete Tests
    
    func testDelete_ById_Found() {
        // Given: Entity in database
        let id = UUID().uuidString
        NotificationContent.create(
            in: context,
            id: id,
            scheduledTime: Date()
        )
        try! context.save()
        
        // When: Delete by id
        let deleted = NotificationContent.delete(by: id, in: context)
        
        // Then: Should be deleted
        XCTAssertTrue(deleted, "Should delete entity")
        
        // Verify deleted
        let fetched = NotificationContent.fetch(by: id, in: context)
        XCTAssertNil(fetched, "Entity should be deleted")
    }
    
    func testDelete_ById_NotFound() {
        // Given: No entity in database
        
        // When: Delete by non-existent id
        let deleted = NotificationContent.delete(by: "non-existent", in: context)
        
        // Then: Should return false
        XCTAssertFalse(deleted, "Should return false for non-existent id")
    }
    
    func testDeleteAll() {
        // Given: Multiple entities
        for i in 1...5 {
            NotificationContent.create(
                in: context,
                id: "id-\(i)",
                scheduledTime: Date()
            )
        }
        try! context.save()
        
        // When: Delete all
        let count = NotificationContent.deleteAll(in: context)
        
        // Then: Should delete all
        XCTAssertEqual(count, 5, "Should delete 5 entities")
        
        // Verify all deleted
        let all = NotificationContent.fetchAll(in: context)
        XCTAssertEqual(all.count, 0, "Should be empty")
    }
}

