import { jest } from '@jest/globals'
import P from 'pino'
import type { SessionActivityMetadata } from '../../Signal/session-activity-tracker'
import { makeSessionActivityTracker } from '../../Signal/session-activity-tracker'
import type { SignalKeyStoreWithTransaction } from '../../Types'

const mockKeys: jest.Mocked<SignalKeyStoreWithTransaction> = {
	get: jest.fn() as any,
	set: jest.fn() as any,
	transaction: jest.fn(async (work: () => any) => await work()) as any,
	isInTransaction: jest.fn() as any
}

const logger = P({ level: 'silent' })

describe('SessionActivityTracker', () => {
	beforeEach(() => {
		jest.clearAllMocks()
		jest.useFakeTimers()
	})

	afterEach(() => {
		jest.useRealTimers()
	})

	describe('recordActivity', () => {
		it('should record activity in memory cache', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const jid = '5511999999999@s.whatsapp.net'

			tracker.recordActivity(jid)

			const stats = tracker.getStats()
			expect(stats.totalUpdates).toBe(1)
			expect(stats.cacheSize).toBe(1)
		})

		it('should update existing activity timestamp', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const jid = '5511999999999@s.whatsapp.net'

			tracker.recordActivity(jid)
			jest.advanceTimersByTime(5000) // 5 seconds later
			tracker.recordActivity(jid)

			const stats = tracker.getStats()
			expect(stats.totalUpdates).toBe(2)
			expect(stats.cacheSize).toBe(1) // Still one unique JID
		})

		it('should not record activity when disabled', () => {
			const config = { enabled: false, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.recordActivity('5511999999999@s.whatsapp.net')

			const stats = tracker.getStats()
			expect(stats.totalUpdates).toBe(0)
			expect(stats.cacheSize).toBe(0)
		})

		it('should handle multiple JIDs', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.recordActivity('5511999999999@s.whatsapp.net')
			tracker.recordActivity('5511888888888@s.whatsapp.net')
			tracker.recordActivity('123456789@lid')

			const stats = tracker.getStats()
			expect(stats.totalUpdates).toBe(3)
			expect(stats.cacheSize).toBe(3)
		})
	})

	describe('getLastActivity', () => {
		it('should return activity from cache (cache hit)', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const jid = '5511999999999@s.whatsapp.net'
			const beforeTime = Date.now()

			tracker.recordActivity(jid)

			const lastActivity = await tracker.getLastActivity(jid)
			expect(lastActivity).toBeGreaterThanOrEqual(beforeTime)
			expect(mockKeys.get).not.toHaveBeenCalled() // Cache hit, no DB call
		})

		it('should fallback to disk when not in cache (cache miss)', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const jid = '5511999999999@s.whatsapp.net'
			const diskTimestamp = Date.now() - 10000

			// @ts-ignore
			mockKeys.get.mockResolvedValue({
				'session-activity:5511999999999@s.whatsapp.net': {
					lastActivityAt: diskTimestamp,
					createdAt: diskTimestamp
				} as SessionActivityMetadata
			})

			const lastActivity = await tracker.getLastActivity(jid)
			expect(lastActivity).toBe(diskTimestamp)
			expect(mockKeys.get).toHaveBeenCalledTimes(1)
		})

		it('should return undefined when activity not found', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			// @ts-ignore
			mockKeys.get.mockResolvedValue({})

			const lastActivity = await tracker.getLastActivity('nonexistent@s.whatsapp.net')
			expect(lastActivity).toBeUndefined()
		})

		it('should return undefined when disabled', async () => {
			const config = { enabled: false, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const lastActivity = await tracker.getLastActivity('5511999999999@s.whatsapp.net')
			expect(lastActivity).toBeUndefined()
		})

		it('should handle disk read errors gracefully', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			// @ts-ignore
			mockKeys.get.mockRejectedValue(new Error('Disk read error'))

			const lastActivity = await tracker.getLastActivity('5511999999999@s.whatsapp.net')
			expect(lastActivity).toBeUndefined()
		})
	})

	describe('getAllActivities', () => {
		it('should return all activities from disk and cache', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const now = Date.now()

			// Simulate disk data
			// @ts-ignore
			mockKeys.get.mockResolvedValue({
				'session-activity:5511999999999@s.whatsapp.net': {
					lastActivityAt: now - 10000,
					createdAt: now - 20000
				} as SessionActivityMetadata,
				'session-activity:5511888888888@s.whatsapp.net': {
					lastActivityAt: now - 5000,
					createdAt: now - 15000
				} as SessionActivityMetadata
			})

			// Add new activity to cache
			tracker.recordActivity('123456789@lid')

			const activities = await tracker.getAllActivities()

			expect(activities.size).toBe(3)
			expect(activities.has('5511999999999@s.whatsapp.net')).toBe(true)
			expect(activities.has('5511888888888@s.whatsapp.net')).toBe(true)
			expect(activities.has('123456789@lid')).toBe(true)
		})

		it('should prioritize cache over disk (cache is more recent)', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const jid = '5511999999999@s.whatsapp.net'
			const diskTimestamp = Date.now() - 10000

			// Simulate old disk data
			// @ts-ignore
			mockKeys.get.mockResolvedValue({
				[`session-activity:${jid}`]: {
					lastActivityAt: diskTimestamp,
					createdAt: diskTimestamp
				} as SessionActivityMetadata
			})

			// Record new activity in cache
			const beforeCache = Date.now()
			tracker.recordActivity(jid)

			const activities = await tracker.getAllActivities()
			const cacheTimestamp = activities.get(jid)

			expect(cacheTimestamp).toBeGreaterThanOrEqual(beforeCache)
			expect(cacheTimestamp).not.toBe(diskTimestamp) // Cache wins
		})

		it('should return empty map when disabled', async () => {
			const config = { enabled: false, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const activities = await tracker.getAllActivities()
			expect(activities.size).toBe(0)
		})

		it('should handle disk read errors gracefully', async () => {
			// Use real timers for this test
			jest.useRealTimers()

			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			// Simulate disk error
			// @ts-ignore
			mockKeys.get.mockRejectedValue(new Error('Disk read error'))

			// getAllActivities should return empty map (logs warning but doesn't throw)
			const activities = await tracker.getAllActivities()
			expect(activities).toBeInstanceOf(Map)
			expect(activities.size).toBe(0) // Current implementation doesn't return cache on disk error

			// Restore fake timers
			jest.useFakeTimers()
		})
	})

	describe('flush', () => {
		it('should flush cache to disk in batch', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const jid1 = '5511999999999@s.whatsapp.net'
			const jid2 = '5511888888888@s.whatsapp.net'

			tracker.recordActivity(jid1)
			tracker.recordActivity(jid2)

			await tracker.flush()

			// Should call transaction once for batch write
			expect(mockKeys.transaction).toHaveBeenCalledTimes(1)
			expect(mockKeys.set).toHaveBeenCalledTimes(1)

			// Verify batch update structure
			const setCall = mockKeys.set.mock.calls[0]?.[0]
			// @ts-ignore
			expect(setCall['session-activity']).toBeDefined()
			// @ts-ignore
			expect(setCall['session-activity'][`session-activity:${jid1}`]).toBeDefined()
			// @ts-ignore
			expect(setCall['session-activity'][`session-activity:${jid2}`]).toBeDefined()
		})

		it('should clear cache after successful flush', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.recordActivity('5511999999999@s.whatsapp.net')

			await tracker.flush()

			const stats = tracker.getStats()
			expect(stats.cacheSize).toBe(0) // Cache cleared
			expect(stats.totalFlushes).toBe(1)
		})

		it('should not flush when cache is empty', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			await tracker.flush()

			expect(mockKeys.transaction).not.toHaveBeenCalled()
		})

		it('should not flush when disabled', async () => {
			const config = { enabled: false, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			await tracker.flush()

			expect(mockKeys.transaction).not.toHaveBeenCalled()
		})

		it('should keep cache on flush failure (retry on next flush)', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.recordActivity('5511999999999@s.whatsapp.net')

			// Simulate flush error
			mockKeys.transaction.mockRejectedValueOnce(new Error('Flush failed'))

			await tracker.flush()

			const stats = tracker.getStats()
			expect(stats.cacheSize).toBe(1) // Cache NOT cleared
			expect(stats.totalFlushes).toBe(0) // Flush failed
		})

		it('should update statistics after successful flush', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.recordActivity('5511999999999@s.whatsapp.net')

			const beforeFlush = Date.now()
			await tracker.flush()
			const afterFlush = Date.now()

			const stats = tracker.getStats()
			expect(stats.totalFlushes).toBe(1)
			expect(stats.lastFlushAt).toBeGreaterThanOrEqual(beforeFlush)
			expect(stats.lastFlushAt).toBeLessThanOrEqual(afterFlush)
			expect(stats.lastFlushDuration).toBeGreaterThanOrEqual(0)
		})
	})

	describe('start/stop lifecycle', () => {
		it('should start periodic flush', async () => {
			// Use real timers for this test
			jest.useRealTimers()

			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			// Record activity so there's something to flush
			tracker.recordActivity('5511999999999@s.whatsapp.net')

			tracker.start()

			// Wait for initial flush to complete
			await new Promise(resolve => setTimeout(resolve, 50))

			// Should attempt initial flush
			expect(mockKeys.transaction).toHaveBeenCalled()

			// Cleanup
			await tracker.stop()

			// Restore fake timers
			jest.useFakeTimers()
		})

		it('should flush periodically after start', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.start()
			jest.clearAllMocks()

			// Record activity
			tracker.recordActivity('5511999999999@s.whatsapp.net')

			// Advance time to trigger flush
			jest.advanceTimersByTime(60000)
			await Promise.resolve() // Let flush promise resolve

			expect(mockKeys.transaction).toHaveBeenCalled()
		})

		it('should not start when disabled', () => {
			const config = { enabled: false, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.start()

			expect(mockKeys.transaction).not.toHaveBeenCalled()
		})

		it('should not start twice (guard against multiple start calls)', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.start()
			jest.clearAllMocks()

			// Second start should be ignored
			tracker.start()

			// Should not trigger another initial flush
			expect(mockKeys.transaction).not.toHaveBeenCalled()
		})

		it('should stop periodic flush and flush pending data', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.start()
			jest.clearAllMocks()

			// Record activity
			tracker.recordActivity('5511999999999@s.whatsapp.net')

			// Stop should flush pending data
			await tracker.stop()

			expect(mockKeys.transaction).toHaveBeenCalledTimes(1)
			expect(mockKeys.set).toHaveBeenCalled()
		})

		it('should stop periodic flush without error when no pending data', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.start()
			jest.clearAllMocks()

			await tracker.stop()

			// No flush needed (no pending data)
			expect(mockKeys.transaction).not.toHaveBeenCalled()
		})
	})

	describe('getStats', () => {
		it('should return current statistics', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			tracker.recordActivity('5511999999999@s.whatsapp.net')
			tracker.recordActivity('5511888888888@s.whatsapp.net')

			const stats = tracker.getStats()

			expect(stats.enabled).toBe(true)
			expect(stats.totalUpdates).toBe(2)
			expect(stats.cacheSize).toBe(2)
			expect(stats.totalFlushes).toBe(0) // No flush yet
			expect(stats.lastFlushAt).toBe(0)
			expect(stats.lastFlushDuration).toBe(0)
		})

		it('should show disabled status', () => {
			const config = { enabled: false, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const stats = tracker.getStats()
			expect(stats.enabled).toBe(false)
		})
	})

	describe('Performance', () => {
		it('should handle high-volume activity recording (1000 messages)', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const startTime = Date.now()

			// Simulate 1000 messages
			for (let i = 0; i < 1000; i++) {
				tracker.recordActivity(`5511${i.toString().padStart(9, '0')}@s.whatsapp.net`)
			}

			const duration = Date.now() - startTime

			const stats = tracker.getStats()
			expect(stats.totalUpdates).toBe(1000)
			expect(stats.cacheSize).toBe(1000)

			// Should be very fast (<100ms for 1000 updates)
			expect(duration).toBeLessThan(100)
		})

		it('should batch flush multiple activities in single transaction', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			// Record 100 activities
			for (let i = 0; i < 100; i++) {
				tracker.recordActivity(`5511${i.toString().padStart(9, '0')}@s.whatsapp.net`)
			}

			await tracker.flush()

			// Should use single transaction for all 100
			expect(mockKeys.transaction).toHaveBeenCalledTimes(1)
			expect(mockKeys.set).toHaveBeenCalledTimes(1)

			const stats = tracker.getStats()
			expect(stats.totalFlushes).toBe(1)
		})
	})

	describe('Edge Cases', () => {
		it('should handle JID with special characters', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const specialJids = [
				'5511999999999:1@s.whatsapp.net', // Device ID
				'123456789@lid', // LID
				'5511999999999:99@hosted', // Hosted device
				'123456789:5@hosted.lid' // Hosted LID
			]

			specialJids.forEach(jid => tracker.recordActivity(jid))

			const stats = tracker.getStats()
			expect(stats.cacheSize).toBe(4)
		})

		it('should handle rapid updates to same JID', () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			const jid = '5511999999999@s.whatsapp.net'

			// Rapid updates (10 in quick succession)
			for (let i = 0; i < 10; i++) {
				tracker.recordActivity(jid)
			}

			const stats = tracker.getStats()
			expect(stats.totalUpdates).toBe(10)
			expect(stats.cacheSize).toBe(1) // Still one JID
		})

		it('should handle flush during active recording', async () => {
			const config = { enabled: true, flushIntervalMs: 60000 }
			const tracker = makeSessionActivityTracker(mockKeys, logger, config)

			// Record activity
			tracker.recordActivity('5511999999999@s.whatsapp.net')

			// Flush clears the cache
			await tracker.flush()

			// Record new activity after flush
			tracker.recordActivity('5511888888888@s.whatsapp.net')

			// New activity should be in cache
			const stats = tracker.getStats()
			expect(stats.cacheSize).toBe(1) // Only the new one
		})
	})
})
