//
//  DatabaseSnapshotManagerTests.swift
//  damus
//
//  Created by Daniel D'Aquino on 2026-01-02.
//

import XCTest
@testable import damus

class DatabaseSnapshotManagerTests: XCTestCase {
    
    var tempDirectory: URL!
    var manager: DatabaseSnapshotManager!
    var testNdb: Ndb!
    
    /// Helper function to collect note IDs from a database subscription until expected notes are found or timeout occurs.
    /// - Parameters:
    ///   - ndb: The database instance to subscribe to
    ///   - filters: The filters to use for subscription
    ///   - expectedNoteIds: The set of note IDs we expect to find
    ///   - expectation: The XCTestExpectation to fulfill when all notes are found
    ///   - timeout: Maximum time to wait in seconds (default: 5.0)
    /// - Returns: The set of collected note IDs
    private func collectNoteIds(
        from ndb: Ndb,
        filters: [NdbFilter],
        expectedNoteIds: Set<NoteId>,
        expectation: XCTestExpectation,
        timeout: TimeInterval = 5.0
    ) -> Task<Set<NoteId>, Never> {
        Task {
            await withCheckedContinuation { continuation in
                var collectedNoteIds = Set<NoteId>()
                var hasReturned = false
                
                // Timeout handler
                Task {
                    try? await Task.sleep(for: .seconds(timeout))
                    guard !hasReturned else { return }
                    hasReturned = true
                    print("⚠️ Timeout: Expected \(expectedNoteIds.count) notes, collected \(collectedNoteIds.count)")
                    continuation.resume(returning: collectedNoteIds)
                }
                
                // Subscription handler
                Task {
                    do {
                        for await item in try ndb.subscribe(filters: filters) {
                            guard !hasReturned else { break }
                            
                            switch item {
                            case .eose:
                                continue
                            case .event(let noteKey):
                                try ndb.lookup_note_by_key(noteKey, borrow: { unownedNote in
                                    switch unownedNote {
                                    case .none:
                                        return
                                    case .some(let unownedNote):
                                        collectedNoteIds.insert(unownedNote.id)
                                    }
                                })
                            }
                            
                            if collectedNoteIds == expectedNoteIds {
                                hasReturned = true
                                expectation.fulfill()
                                continuation.resume(returning: collectedNoteIds)
                            }
                        }
                    } catch {
                        guard !hasReturned else { return }
                        hasReturned = true
                        XCTFail("Note streaming failed: \(error)")
                        continuation.resume(returning: collectedNoteIds)
                    }
                }
            }
        }
    }
    
    override func setUp() async throws {
        try await super.setUp()
        
        // Create a temporary directory for test files
        tempDirectory = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString, conformingTo: .directory)
        try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
        
        self.testNdb = Ndb(path: test_ndb_dir(), owns_db_file: true)!
        
        // Create the manager
        manager = DatabaseSnapshotManager(ndb: self.testNdb)
        
        // Clear UserDefaults for consistent testing
        UserDefaults.standard.removeObject(forKey: "lastDatabaseSnapshotDate")
    }
    
    override func tearDown() async throws {
        // Clean up temporary directory
        if let tempDirectory = tempDirectory {
            try? FileManager.default.removeItem(at: tempDirectory)
        }
        
        // Clear UserDefaults
        UserDefaults.standard.removeObject(forKey: "lastDatabaseSnapshotDate")
        
        // Stop any running snapshots
        await manager.stopPeriodicSnapshots()
        
        manager = nil
        tempDirectory = nil
        
        try await super.tearDown()
    }
    
    // MARK: - Snapshot Creation Tests
    
    func testCreateSnapshotIfNeeded_CreatesSnapshotWhenNeverCreatedBefore() async throws {
        // Given: No previous snapshot exists
        XCTAssertNil(UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate"))
        
        // When: createSnapshotIfNeeded is called
        try await manager.createSnapshotIfNeeded()
        
        // Then: A snapshot should be created
        XCTAssertNotNil(UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate"))
    }
    
    func testCreateSnapshotIfNeeded_SkipsSnapshotWhenRecentSnapshotExists() async throws {
        // Given: A recent snapshot was just created
        UserDefaults.standard.set(Date(), forKey: "lastDatabaseSnapshotDate")
        
        // When: createSnapshotIfNeeded is called
        let snapshotMade = try await manager.createSnapshotIfNeeded()
        
        // Then: No snapshot should be created
        XCTAssertFalse(snapshotMade)
    }
    
    func testCreateSnapshotIfNeeded_CreatesSnapshotWhenIntervalHasPassed() async throws {
        // Given: A snapshot was created more than 1 hour ago
        let oldDate = Date().addingTimeInterval(-60 * 61) // 61 minutes ago
        UserDefaults.standard.set(oldDate, forKey: "lastDatabaseSnapshotDate")
        
        // When: createSnapshotIfNeeded is called
        let snapshotMade = try await manager.createSnapshotIfNeeded()
        
        // Then: A snapshot should be created
        XCTAssertTrue(snapshotMade)
        
        // And: The last snapshot date should be updated
        let lastDate = UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate") as? Date
        XCTAssertNotNil(lastDate)
        XCTAssertTrue(lastDate! > oldDate)
    }
    
    // MARK: - Perform Snapshot Tests
    
    func testPerformSnapshot_WritesFile() async throws {
        // Given: No previous snapshot exists
        XCTAssertNil(UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate"))
        let fileManager = FileManager.default
        guard let snapshotPath = Ndb.snapshot_db_path else {
            XCTFail("Snapshot path should be available")
            return
        }
        try fileManager.removeItem(atPath: snapshotPath)
        XCTAssertFalse(fileManager.fileExists(atPath: snapshotPath), "Snapshot directory should not exist at \(snapshotPath)")
        
        
        // When: Creating a snapshot
        let snapshotMade = try await manager.createSnapshotIfNeeded()
        
        // Then: Snapshot should be created
        XCTAssertTrue(snapshotMade)
        
        // And: The snapshot should be there
        var isDirectory: ObjCBool = false
        let exists = fileManager.fileExists(atPath: snapshotPath, isDirectory: &isDirectory)
        
        XCTAssertTrue(exists, "Snapshot directory should exist at \(snapshotPath)")
        XCTAssertTrue(isDirectory.boolValue, "Snapshot path should be a directory")
        
        // And: LMDB database files should exist
        let dataFile = "\(snapshotPath)/data.mdb"
        XCTAssertTrue(fileManager.fileExists(atPath: dataFile), "data.mdb should exist")
    }
    
    func testPerformSnapshot_UpdatesTimestamp() async throws {
        // Given: No previous snapshot
        XCTAssertNil(UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate"))
        
        let beforeDate = Date()
        
        // When: Performing a snapshot
        try await manager.performSnapshot()
        
        let afterDate = Date()
        
        // Then: The timestamp should be set and within the time window
        let savedDate = UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate") as? Date
        XCTAssertNotNil(savedDate)
        XCTAssertGreaterThanOrEqual(savedDate!, beforeDate)
        XCTAssertLessThanOrEqual(savedDate!, afterDate)
    }
    
    func testPerformSnapshot_CanBeCalledMultipleTimes() async throws {
        // Given: A snapshot already exists
        try await manager.performSnapshot()
        
        // When: Performing another snapshot (this should replace the old one)
        try await manager.performSnapshot()
        
        // Then: No error should occur
        XCTAssertNotNil(UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate"))
    }
    
    // MARK: - Periodic Snapshot Timer Tests
    
    func testStartPeriodicSnapshots_StartsTimer() async throws {
        // Given: Manager is initialized
        
        
        // When: startPeriodicSnapshots is called
        await manager.startPeriodicSnapshots()
        
        // Give the timer task a moment to execute
        try await Task.sleep(for: .milliseconds(100))
        
        // Then: A snapshot should be attempted
        let tickCount = await manager.snapshotTimerTickCount
        XCTAssertGreaterThan(tickCount, 0)
    }
    
    func testStartPeriodicSnapshots_DoesNotStartMultipleTimes() async throws {
        // Given: Timer is already started
        await manager.startPeriodicSnapshots()
        
        // Give the timer a moment to start
        try await Task.sleep(for: .milliseconds(500))
        
        let firstTickCount = await manager.snapshotTimerTickCount
        
        // When: startPeriodicSnapshots is called again
        await manager.startPeriodicSnapshots()
        
        // Give it a moment
        try await Task.sleep(for: .milliseconds(500))
        
        let secondTickCount = await manager.snapshotTimerTickCount
        
        // Then: The tick count should not have increased significantly
        // (proving we didn't start a second timer)
        XCTAssertEqual(secondTickCount, firstTickCount, "Starting twice should not create multiple timers")
    }
    
    func testStopPeriodicSnapshots_StopsTimer() async throws {
        // Given: Timer is running
        await manager.startPeriodicSnapshots()
        
        // When: stopPeriodicSnapshots is called and stats are reset
        await manager.stopPeriodicSnapshots()
        await manager.resetStats()
        
        // Wait longer than the timer interval
        try await Task.sleep(for: .milliseconds(200))
        
        // Then: No more snapshots should be created
        let snapshotCount = await manager.snapshotCount
        XCTAssertEqual(snapshotCount, 0)
    }
    
    func testStopPeriodicSnapshots_CanBeCalledMultipleTimes() async throws {
        // Given: Timer is running
        await manager.startPeriodicSnapshots()
        
        // When: stopPeriodicSnapshots is called multiple times
        await manager.stopPeriodicSnapshots()
        await manager.stopPeriodicSnapshots()
        
        // Then: No crash should occur (test passes if we get here)
        XCTAssertTrue(true)
    }
    
    // MARK: - Integration Tests
    
    func testSnapshotLifecycle_StartStopRestart() async throws {
        // Given: A manager with valid configuration
        
        // When: Starting, stopping, and restarting the timer
        await manager.startPeriodicSnapshots()
        try await Task.sleep(for: .milliseconds(100))
        
        await manager.stopPeriodicSnapshots()
        
        await manager.startPeriodicSnapshots()
        try await Task.sleep(for: .milliseconds(1000))
        
        // Then: Snapshots should be created appropriately
        let snapshotCount = await manager.snapshotCount
        XCTAssertGreaterThan(snapshotCount, 0)
    }
    
    func testSnapshotTimestampUpdates() async throws {
        // Given: No previous snapshot
        XCTAssertNil(UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate"))
        
        // When: Creating first snapshot
        try await manager.performSnapshot()
        let firstDate = UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate") as? Date
        XCTAssertNotNil(firstDate)
        
        // Wait to ensure time difference
        try await Task.sleep(for: .milliseconds(500))
        
        // Force another snapshot by clearing the date
        UserDefaults.standard.removeObject(forKey: "lastDatabaseSnapshotDate")
        try await manager.performSnapshot()
        let secondDate = UserDefaults.standard.object(forKey: "lastDatabaseSnapshotDate") as? Date
        XCTAssertNotNil(secondDate)
        
        // Then: Second date should be after first date
        XCTAssertGreaterThan(secondDate!, firstDate!, "Second snapshot timestamp should be later than first")
    }
    
    // MARK: - Error Handling Tests
    
    func testCreateSnapshotIfNeeded_HandlesErrors() async throws {
        // This test verifies that errors from performSnapshot are propagated
        // We can't easily test the actual error cases without mocking,
        // but we verify the method signature allows throwing
        
        // Given: A recent snapshot exists
        UserDefaults.standard.set(Date(), forKey: "lastDatabaseSnapshotDate")
        
        // When: Attempting to create a snapshot (should skip)
        let result = try await manager.createSnapshotIfNeeded()
        
        // Then: Should return false without throwing
        XCTAssertFalse(result)
    }
    
    // MARK: - Selective Snapshot Content Tests
    
    func testPerformSnapshot_ContainsOnlyRelevantNoteTypes() async throws {
        // Given: A database with various note types
        let profileNote = NostrEvent(content: "{\"name\":\"Test User\"}", keypair: test_keypair, kind: NostrKind.metadata.rawValue)!
        let textNote = NostrEvent(content: "Hello world", keypair: test_keypair, kind: NostrKind.text.rawValue)!
        let contactsNote = NostrEvent(content: "", keypair: test_keypair, kind: NostrKind.contacts.rawValue)!
        let muteListNote = NostrEvent(content: "", keypair: test_keypair, kind: NostrKind.mute_list.rawValue)!
        
        let profileFilter = try NdbFilter(from: NostrFilter(kinds: [.metadata]))
        let contactsFilter = try NdbFilter(from: NostrFilter(kinds: [.contacts]))
        let muteListFilter = try NdbFilter(from: NostrFilter(kinds: [.mute_list]))
        let textFilter = try NdbFilter(from: NostrFilter(kinds: [.text]))
        
        // Process notes into source database
        let expectedIngestedNotes = [profileNote, textNote, contactsNote, muteListNote]
        let expectedSnapshottedNotes = [profileNote, contactsNote, muteListNote]
        
        let expectedIngestedNoteIds = Set(expectedIngestedNotes.map { $0.id })
        let expectedSnapshottedNoteIds = Set(expectedSnapshottedNotes.map { $0.id })
        
        let allNotesAreIngestedInSourceDB = XCTestExpectation(description: "All notes are ingested in source DB")
        let ingestTask = collectNoteIds(
            from: testNdb,
            filters: [profileFilter, contactsFilter, muteListFilter, textFilter],
            expectedNoteIds: expectedIngestedNoteIds,
            expectation: allNotesAreIngestedInSourceDB
        )
        
        for note in expectedIngestedNotes {
            try testNdb.add(event: note)
        }
        
        await fulfillment(of: [allNotesAreIngestedInSourceDB], timeout: 5)
        let ingestedNoteIds = await ingestTask.value
        XCTAssertEqual(expectedIngestedNoteIds, ingestedNoteIds)
        
        guard let snapshotPath = Ndb.snapshot_db_path else {
            XCTFail("Snapshot path should be available")
            return
        }
        
        // When: Creating a snapshot
        try await manager.performSnapshot()
        
        // Then: Snapshot database should exist
        XCTAssertTrue(FileManager.default.fileExists(atPath: snapshotPath))
        
        // And: Snapshot should contain only profiles (kind 0), contacts (kind 3), and mute lists (kind 10000)
        guard let snapshotNdb = Ndb(path: snapshotPath, owns_db_file: false) else {
            XCTFail("Should be able to open snapshot database")
            return
        }
        defer { snapshotNdb.close() }
        
        let allNotesAreSnapshottedToSnapshotDB = XCTestExpectation(description: "All notes are snapshotted to snapshot DB")
        let snapshotTask = collectNoteIds(
            from: snapshotNdb,
            filters: [profileFilter, contactsFilter, muteListFilter, textFilter],
            expectedNoteIds: expectedSnapshottedNoteIds,
            expectation: allNotesAreSnapshottedToSnapshotDB
        )
        
        await fulfillment(of: [allNotesAreSnapshottedToSnapshotDB], timeout: 5)
        let snapshottedNoteIds = await snapshotTask.value
        XCTAssertEqual(expectedSnapshottedNoteIds, snapshottedNoteIds)
    }
    
    func testPerformSnapshot_HandlesEmptyDatabase() async throws {
        // Given: An empty database with no notes
        guard let snapshotPath = Ndb.snapshot_db_path else {
            XCTFail("Snapshot path should be available")
            return
        }
        
        // When: Creating a snapshot of an empty database
        try await manager.performSnapshot()
        
        // Then: Snapshot should be created successfully
        XCTAssertTrue(FileManager.default.fileExists(atPath: snapshotPath))
        
        // And: Snapshot should be accessible but contain no notes
        guard let snapshotNdb = Ndb(path: snapshotPath, owns_db_file: false) else {
            XCTFail("Should be able to open snapshot database")
            return
        }
        defer { snapshotNdb.close() }
        
        let allFilter = try NdbFilter(from: NostrFilter())
        let allKeys = try snapshotNdb.query(filters: [allFilter], maxResults: 100)
        XCTAssertEqual(allKeys.count, 0, "Empty database snapshot should contain no notes")
    }
    
    func testPerformSnapshot_HandlesLargeNumberOfNotes() async throws {
        // Given: A database with many profile notes
        var profileNotes: [NostrEvent] = []
        for i in 0..<2000 {
            let profileNote = NostrEvent(content: "{\"name\":\"User \(i)\"}", keypair: generate_new_keypair().to_keypair(), kind: 0)!
            profileNotes.append(profileNote)
        }
        
        let profileFilter = try NdbFilter(from: NostrFilter(kinds: [.metadata]))
        let expectedNoteIds = Set(profileNotes.map { $0.id })
        let allNotesIngested = XCTestExpectation(description: "All 2000 profile notes are ingested")
        
        let ingestTask = collectNoteIds(
            from: testNdb,
            filters: [profileFilter],
            expectedNoteIds: expectedNoteIds,
            expectation: allNotesIngested
        )
        
        for profileNote in profileNotes {
            try testNdb.add(event: profileNote)
        }
        
        // Wait for all notes to be ingested before snapshot
        await fulfillment(of: [allNotesIngested], timeout: 10)
        let ingestedNoteIds = await ingestTask.value
        XCTAssertEqual(expectedNoteIds, ingestedNoteIds, "All 2000 profile notes should be ingested")
        
        guard let snapshotPath = Ndb.snapshot_db_path else {
            XCTFail("Snapshot path should be available")
            return
        }
        
        // When: Creating a snapshot
        try await manager.performSnapshot()
        
        // Then: Snapshot should contain all profile notes
        guard let snapshotNdb = Ndb(path: snapshotPath, owns_db_file: false) else {
            XCTFail("Should be able to open snapshot database")
            return
        }
        defer { snapshotNdb.close() }
        
        let profileKeys = try snapshotNdb.query(filters: [profileFilter], maxResults: 100_000)
        XCTAssertEqual(profileKeys.count, 2000, "Snapshot should contain all 2000 profile notes")
    }
    
    // MARK: - Edge Case Tests
    
    func testSnapshotInterval_BoundaryCondition() async throws {
        // Given: A snapshot was created exactly 1 hour ago (the minimum interval)
        let exactlyOneHourAgo = Date().addingTimeInterval(-60 * 60)
        UserDefaults.standard.set(exactlyOneHourAgo, forKey: "lastDatabaseSnapshotDate")
        
        // When: Attempting to create a snapshot at the exact boundary
        let shouldCreate = try await manager.createSnapshotIfNeeded()
        
        // Then: A snapshot should be created (>= rather than > comparison)
        XCTAssertTrue(shouldCreate, "Snapshot should be created when exactly at minimum interval")
    }
    
    func testSnapshotInterval_JustBeforeBoundary() async throws {
        // Given: A snapshot was created 59 minutes and 59 seconds ago (just before the interval)
        let justBeforeOneHour = Date().addingTimeInterval(-60 * 59 - 59)
        UserDefaults.standard.set(justBeforeOneHour, forKey: "lastDatabaseSnapshotDate")
        
        // When: Attempting to create a snapshot
        let shouldCreate = try await manager.createSnapshotIfNeeded()
        
        // Then: No snapshot should be created
        XCTAssertFalse(shouldCreate, "Snapshot should not be created before minimum interval")
    }
    
    func testPerformSnapshot_ReplacesExistingSnapshot() async throws {
        // Given: A snapshot already exists with a profile note
        let firstProfileNote = NostrEvent(content: "{\"name\":\"First User\"}", keypair: generate_new_keypair().to_keypair(), kind: 0)!
        
        let profileFilter = try NdbFilter(from: NostrFilter(kinds: [.metadata]))
        let firstNoteIds = Set([firstProfileNote.id])
        let firstNoteIngested = XCTestExpectation(description: "First note is ingested")
        
        let firstIngestTask = collectNoteIds(
            from: testNdb,
            filters: [profileFilter],
            expectedNoteIds: firstNoteIds,
            expectation: firstNoteIngested
        )
        
        try testNdb.add(event: firstProfileNote)
        
        await fulfillment(of: [firstNoteIngested], timeout: 5)
        let firstIngestedNoteIds = await firstIngestTask.value
        XCTAssertEqual(firstNoteIds, firstIngestedNoteIds, "First profile note should be ingested")
        
        try await manager.performSnapshot()
        
        guard let snapshotPath = Ndb.snapshot_db_path else {
            XCTFail("Snapshot path should be available")
            return
        }
        
        // Add a new profile note to the source database
        let secondProfileNote = NostrEvent(content: "{\"name\":\"Second User\"}", keypair: generate_new_keypair().to_keypair(), kind: 0)!
        
        let bothNoteIds = Set([firstProfileNote.id, secondProfileNote.id])
        let secondNoteIngested = XCTestExpectation(description: "Second note is ingested")
        
        let secondIngestTask = collectNoteIds(
            from: testNdb,
            filters: [profileFilter],
            expectedNoteIds: bothNoteIds,
            expectation: secondNoteIngested
        )
        
        try testNdb.add(event: secondProfileNote)
        
        await fulfillment(of: [secondNoteIngested], timeout: 5)
        let secondIngestedNoteIds = await secondIngestTask.value
        XCTAssertEqual(bothNoteIds, secondIngestedNoteIds, "Both profile notes should be ingested")
        
        // When: Creating another snapshot
        try await manager.performSnapshot()
        
        // Then: New snapshot should replace the old one and contain both notes
        guard let snapshotNdb = Ndb(path: snapshotPath, owns_db_file: false) else {
            XCTFail("Should be able to open snapshot database")
            return
        }
        defer { snapshotNdb.close() }
        
        let expectedNoteIds = Set([firstProfileNote.id, secondProfileNote.id])
        let allNotesAreInSnapshot = XCTestExpectation(description: "All notes are in snapshot")
        
        let snapshotTask = collectNoteIds(
            from: snapshotNdb,
            filters: [profileFilter],
            expectedNoteIds: expectedNoteIds,
            expectation: allNotesAreInSnapshot
        )
        
        await fulfillment(of: [allNotesAreInSnapshot], timeout: 5)
        let snapshottedNoteIds = await snapshotTask.value
        XCTAssertEqual(expectedNoteIds, snapshottedNoteIds, "Snapshot should contain both profile notes")
    }

    func testPerformSnapshot_RemovesStaleTemporarySnapshotDirectories() async throws {
        let fileManager = FileManager.default
        let staleTempSnapshotURL = fileManager.temporaryDirectory
            .appendingPathComponent("snapshot_temp_\(UUID().uuidString)", conformingTo: .directory)

        try fileManager.createDirectory(at: staleTempSnapshotURL, withIntermediateDirectories: true)
        try fileManager.setAttributes(
            [.modificationDate: Date().addingTimeInterval(-(60 * 60 * 25))],
            ofItemAtPath: staleTempSnapshotURL.path
        )

        XCTAssertTrue(fileManager.fileExists(atPath: staleTempSnapshotURL.path))

        try await manager.performSnapshot()

        XCTAssertFalse(fileManager.fileExists(atPath: staleTempSnapshotURL.path), "Stale temporary snapshot directory should be cleaned up before snapshotting")
    }

    func testPerformSnapshot_KeepsRecentTemporarySnapshotDirectories() async throws {
        let fileManager = FileManager.default
        let recentTempSnapshotURL = fileManager.temporaryDirectory
            .appendingPathComponent("snapshot_temp_\(UUID().uuidString)", conformingTo: .directory)

        try fileManager.createDirectory(at: recentTempSnapshotURL, withIntermediateDirectories: true)
        defer {
            try? fileManager.removeItem(at: recentTempSnapshotURL)
        }

        try await manager.performSnapshot()

        XCTAssertTrue(fileManager.fileExists(atPath: recentTempSnapshotURL.path), "Recent temporary snapshot directory should not be cleaned up")
    }
}


// MARK: - SnapshotError Equatable Conformance for Testing

extension SnapshotError: Equatable {
    public static func == (lhs: SnapshotError, rhs: SnapshotError) -> Bool {
        switch (lhs, rhs) {
        case (.pathsUnavailable, .pathsUnavailable):
            return true
        case (.copyFailed, .copyFailed):
            return true
        case (.removeFailed, .removeFailed):
            return true
        case (.directoryCreationFailed, .directoryCreationFailed):
            return true
        case (.failedToCreateSnapshotDatabase, .failedToCreateSnapshotDatabase):
            return true
        case (.moveFailed, .moveFailed):
            return true
        default:
            return false
        }
    }
}
