@testable import AppSessionCountClient
import Dependencies
import Foundation
import Testing

@Suite("AppSessionCountClient Tests", .serialized)
struct AppSessionCountClientTests {
    // MARK: - Session Start Type Tests

    @Suite("Session Start Type", .serialized)
    struct SessionStartType {
        @Test("Cold start session detection")
        func testColdStartSessionDetection() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            // First, track finish launching
            await client.trackFinishLaunching()

            // When becoming active after finish launching, should be cold start
            let sessionType = await client.startSessionFromBecomeActive(.unauthenticated)
            #expect(sessionType == .coldStart)

            let sessionData = await client.getSessionData()
            #expect(sessionData.totalSessions == 1)
            #expect(sessionData.coldStartSessions == 1)
            #expect(sessionData.refocusSessions == 0)
        }

        @Test("Refocus session detection")
        func testRefocusSessionDetection() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            // Start with a cold start session
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.unauthenticated)

            // Subsequent become active without finish launching should be refocus
            let sessionType = await client.startSessionFromBecomeActive(.unauthenticated)
            #expect(sessionType == .refocus)

            let sessionData = await client.getSessionData()
            #expect(sessionData.totalSessions == 2)
            #expect(sessionData.coldStartSessions == 1)
            #expect(sessionData.refocusSessions == 1)
        }
    }

    // MARK: - Authentication State Tests

    @Suite("Authentication State", .serialized)
    struct AuthenticationState {
        @Test("Unauthenticated session tracking")
        func testUnauthenticatedSessionTracking() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.unauthenticated)

            let sessionData = await client.getSessionData()
            #expect(sessionData.unauthenticatedSessions == 1)
            #expect(sessionData.authenticatedSessions == 0)
            #expect(sessionData.totalUniqueAuthenticatedUsers == 0)
        }

        @Test("Authenticated session tracking")
        func testAuthenticatedSessionTracking() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            let sessionData = await client.getSessionData()
            #expect(sessionData.authenticatedSessions == 1)
            #expect(sessionData.unauthenticatedSessions == 0)
            #expect(sessionData.totalUniqueAuthenticatedUsers == 1)
            #expect(sessionData.firstTimeLoginSessions == 1)
        }

        @Test("Multiple users tracking")
        func testMultipleUsersTracking() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let user1 = "user1"
            let user2 = "user2"

            // Start sessions for two different users
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: user1))

            _ = await client.startSessionFromBecomeActive(.authenticated(userId: user2))

            let sessionData = await client.getSessionData()
            #expect(sessionData.totalUniqueAuthenticatedUsers == 2)
            #expect(sessionData.firstTimeLoginSessions == 2)
            #expect(sessionData.authenticatedSessions == 2)
        }
    }

    // MARK: - User Session History Tests

    @Suite("User Session History", .serialized)
    struct UserSessionHistory {
        @Test("User session history tracking")
        func testUserSessionHistoryTracking() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            // Start multiple sessions for the same user
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            let sessionData = await client.getSessionData()
            let userHistory = sessionData.userSessionHistories[userId]

            #expect(userHistory != nil)
            #expect(userHistory?.totalSessions == 2)
            #expect(userHistory?.coldStartSessions == 1)
            #expect(userHistory?.refocusSessions == 1)
            #expect(userHistory?.firstAuthenticatedSessionDate != nil)
            #expect(userHistory?.lastSessionDate != nil)
        }

        @Test("First time user detection")
        func testFirstTimeUserDetection() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "newUser"

            // Check if user is first-time before any sessions
            let isFirstTimeBefore = await client.isFirstTimeUser(userId)
            #expect(isFirstTimeBefore)

            // Start session for the user
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            // Check if user is no longer first-time
            let isFirstTimeAfter = await client.isFirstTimeUser(userId)
            #expect(!isFirstTimeAfter)

            let sessionData = await client.getSessionData()
            #expect(sessionData.firstTimeLoginSessions == 1)
        }

        @Test("User session counts")
        func testUserSessionCounts() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            // Start multiple sessions
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            let totalSessions = await client.getUserSessionCount(userId)
            let coldStartSessions = await client.getUserColdStartSessionCount(userId)

            #expect(totalSessions == 2)
            #expect(coldStartSessions == 1)
        }
    }

    // MARK: - Authentication Transition Tests

    @Suite("Authentication Transitions", .serialized)
    struct AuthenticationTransitions {
        @Test("Transition from unauthenticated to authenticated")
        func testTransitionUnauthenticatedToAuthenticated() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            // Start unauthenticated session
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.unauthenticated)

            // Transition to authenticated
            await client.trackAuthenticationTransition(
                .unauthenticated,
                .authenticated(userId: userId)
            )

            let sessionData = await client.getSessionData()
            #expect(sessionData.sessionsWithAuthTransitions == 1)
            #expect(sessionData.firstTimeLoginSessions == 1)
            #expect(sessionData.authenticatedSessions == 1)
            #expect(sessionData.totalUniqueAuthenticatedUsers == 1)
        }

        @Test("Transition from authenticated to unauthenticated")
        func testTransitionAuthenticatedToUnauthenticated() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            // Start authenticated session
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            // Transition to unauthenticated
            await client.trackAuthenticationTransition(
                .authenticated(userId: userId),
                .unauthenticated
            )

            let sessionData = await client.getSessionData()
            #expect(sessionData.sessionsWithAuthTransitions == 1)
            #expect(sessionData.unauthenticatedSessions == 1)
        }

        @Test("Multiple transitions in same session")
        func testMultipleTransitionsInSameSession() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            // Start unauthenticated session
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.unauthenticated)

            // Multiple transitions in the same session
            await client.trackAuthenticationTransition(
                .unauthenticated,
                .authenticated(userId: userId)
            )

            await client.trackAuthenticationTransition(
                .authenticated(userId: userId),
                .unauthenticated
            )

            let sessionData = await client.getSessionData()
            // Should only count as one session with transitions
            #expect(sessionData.sessionsWithAuthTransitions == 1)
        }

        @Test("No transition for same state")
        func testNoTransitionForSameState() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            // Transition to same state should not count
            await client.trackAuthenticationTransition(
                .authenticated(userId: userId),
                .authenticated(userId: userId)
            )

            let sessionData = await client.getSessionData()
            #expect(sessionData.sessionsWithAuthTransitions == 0)
        }
    }

    // MARK: - Complex Scenarios

    @Suite("Complex Scenarios", .serialized)
    struct ComplexScenarios {
        @Test("Complex user journey")
        func testComplexUserJourney() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "user123"

            // Cold start unauthenticated
            await client.trackFinishLaunching()
            let session1Type = await client.startSessionFromBecomeActive(.unauthenticated)
            #expect(session1Type == .coldStart)

            // Login during session
            await client.trackAuthenticationTransition(
                .unauthenticated,
                .authenticated(userId: userId)
            )

            // Refocus session authenticated
            let session2Type = await client.startSessionFromBecomeActive(.authenticated(userId: userId))
            #expect(session2Type == .refocus)

            // Another cold start
            await client.trackFinishLaunching()
            let session3Type = await client.startSessionFromBecomeActive(.authenticated(userId: userId))
            #expect(session3Type == .coldStart)

            let sessionData = await client.getSessionData()

            // Overall counts
            #expect(sessionData.totalSessions == 3)
            #expect(sessionData.coldStartSessions == 2)
            #expect(sessionData.refocusSessions == 1)

            // Auth state counts
            #expect(sessionData.unauthenticatedSessions == 1)
            #expect(sessionData.authenticatedSessions == 3)

            // Transition tracking
            #expect(sessionData.sessionsWithAuthTransitions == 1)
            #expect(sessionData.firstTimeLoginSessions == 1)

            // User specific data
            #expect(sessionData.totalUniqueAuthenticatedUsers == 1)
            let userHistory = sessionData.userSessionHistories[userId]
            #expect(userHistory?.totalSessions == 2)
            #expect(userHistory?.coldStartSessions == 1)
            #expect(userHistory?.refocusSessions == 1)
        }

        @Test("Returning user not counted as first time")
        func testReturningUserNotCountedAsFirstTime() async {
            let client = withDependencies {
                $0.appSessionCountClient = .liveValue
            } operation: {
                @Dependency(\.appSessionCountClient) var client
                return client
            }
            await client.resetSessionData()

            let userId = "returningUser"

            // First session
            await client.trackFinishLaunching()
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            // Second session - should not count as first-time
            _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

            let sessionData = await client.getSessionData()
            #expect(sessionData.firstTimeLoginSessions == 1) // Only counted once
            #expect(sessionData.authenticatedSessions == 2)

            let userHistory = sessionData.userSessionHistories[userId]
            #expect(userHistory?.totalSessions == 2)
        }
    }

    // MARK: - Reset Tests

    @Test("Reset session data")
    func testResetSessionData() async {
        let client = withDependencies {
            $0.appSessionCountClient = .liveValue
        } operation: {
            @Dependency(\.appSessionCountClient) var client
            return client
        }
        await client.resetSessionData()

        let userId = "user123"

        // Create some session data
        await client.trackFinishLaunching()
        _ = await client.startSessionFromBecomeActive(.authenticated(userId: userId))

        // Verify data exists
        var sessionData = await client.getSessionData()
        #expect(sessionData.totalSessions == 1)
        #expect(sessionData.totalUniqueAuthenticatedUsers == 1)

        // Reset data
        await client.resetSessionData()

        // Verify data is cleared
        sessionData = await client.getSessionData()
        #expect(sessionData.totalSessions == 0)
        #expect(sessionData.coldStartSessions == 0)
        #expect(sessionData.refocusSessions == 0)
        #expect(sessionData.unauthenticatedSessions == 0)
        #expect(sessionData.authenticatedSessions == 0)
        #expect(sessionData.totalUniqueAuthenticatedUsers == 0)
        #expect(sessionData.sessionsWithAuthTransitions == 0)
        #expect(sessionData.firstTimeLoginSessions == 0)
        #expect(sessionData.userSessionHistories.isEmpty)
    }
}

// MARK: - Model Tests

@Suite("SessionAuthenticationState Tests")
struct SessionAuthenticationStateTests {
    @Test("User ID property")
    func testUserIdProperty() {
        let unauthenticated = SessionAuthenticationState.unauthenticated
        let authenticated = SessionAuthenticationState.authenticated(userId: "user123")

        #expect(unauthenticated.userId == nil)
        #expect(authenticated.userId == "user123")

        #expect(!unauthenticated.isAuthenticated)
        #expect(authenticated.isAuthenticated)
    }

    @Test("Equality")
    func testEquality() {
        let unauthenticated1 = SessionAuthenticationState.unauthenticated
        let unauthenticated2 = SessionAuthenticationState.unauthenticated
        let authenticated1 = SessionAuthenticationState.authenticated(userId: "user1")
        let authenticated2 = SessionAuthenticationState.authenticated(userId: "user1")
        let authenticated3 = SessionAuthenticationState.authenticated(userId: "user2")

        #expect(unauthenticated1 == unauthenticated2)
        #expect(authenticated1 == authenticated2)
        #expect(authenticated1 != authenticated3)
        #expect(unauthenticated1 != authenticated1)
    }
}

@Suite("AppSessionData Tests")
struct AppSessionDataTests {
    @Test("Initialization")
    func testInitialization() {
        let sessionData = AppSessionData()

        #expect(sessionData.totalSessions == 0)
        #expect(sessionData.coldStartSessions == 0)
        #expect(sessionData.refocusSessions == 0)
        #expect(sessionData.unauthenticatedSessions == 0)
        #expect(sessionData.authenticatedSessions == 0)
        #expect(sessionData.totalUniqueAuthenticatedUsers == 0)
        #expect(sessionData.sessionsWithAuthTransitions == 0)
        #expect(sessionData.firstTimeLoginSessions == 0)
        #expect(sessionData.userSessionHistories.isEmpty)
    }

    @Test("Helper methods")
    func testHelperMethods() {
        var sessionData = AppSessionData()
        let userId = "user123"

        // Initially should be first-time user with 0 sessions
        #expect(sessionData.isFirstTimeUser(userId))
        #expect(sessionData.getUserSessionCount(userId) == 0)
        #expect(sessionData.getUserColdStartSessionCount(userId) == 0)

        // Add user history
        sessionData.userSessionHistories[userId] = UserSessionHistory(
            totalSessions: 5,
            coldStartSessions: 2,
            refocusSessions: 3,
            firstAuthenticatedSessionDate: Date(),
            lastSessionDate: Date()
        )

        // Should no longer be first-time user
        #expect(!sessionData.isFirstTimeUser(userId))
        #expect(sessionData.getUserSessionCount(userId) == 5)
        #expect(sessionData.getUserColdStartSessionCount(userId) == 2)
        #expect(sessionData.totalUniqueAuthenticatedUsers == 1)
    }
}

@Suite("UserSessionHistory Tests")
struct UserSessionHistoryTests {
    @Test("Default initialization")
    func testDefaultInitialization() {
        let history = UserSessionHistory()

        #expect(history.totalSessions == 0)
        #expect(history.coldStartSessions == 0)
        #expect(history.refocusSessions == 0)
        #expect(history.firstAuthenticatedSessionDate == nil)
        #expect(history.lastSessionDate == nil)
    }

    @Test("Custom initialization")
    func testCustomInitialization() {
        let date1 = Date()
        let date2 = Date().addingTimeInterval(3600)

        let history = UserSessionHistory(
            totalSessions: 10,
            coldStartSessions: 4,
            refocusSessions: 6,
            firstAuthenticatedSessionDate: date1,
            lastSessionDate: date2
        )

        #expect(history.totalSessions == 10)
        #expect(history.coldStartSessions == 4)
        #expect(history.refocusSessions == 6)
        #expect(history.firstAuthenticatedSessionDate == date1)
        #expect(history.lastSessionDate == date2)
    }
}
