import XCTest
@testable import ByteLystDiagnostics

final class DiagnosticsClientTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Reset to initial state
        Task {
            // Client is actor, need to await
        }
    }
    
    // MARK: - Singleton Tests
    
    func testSharedInstance() {
        let client1 = DiagnosticsClient.shared
        let client2 = DiagnosticsClient.shared
        XCTAssertTrue(client1 === client2, "Shared instance should be singleton")
    }
    
    // MARK: - Configuration Tests
    
    func testConfiguration() async {
        let config = DiagnosticsConfiguration(
            productId: "test-app",
            anonymousInstallId: "install-123",
            platform: "ios",
            channel: "ios_app",
            osFamily: "ios",
            appVersion: "1.0.0",
            buildNumber: "100",
            releaseChannel: "beta",
            serverUrl: "https://api.test.com"
        )
        
        await DiagnosticsClient.shared.configure(config)
        
        let state = await DiagnosticsClient.shared.getState()
        XCTAssertEqual(state, .idle)
    }
    
    // MARK: - Session State Tests
    
    func testInitialSessionState() async {
        let isActive = await DiagnosticsClient.shared.isSessionActive()
        XCTAssertFalse(isActive)
        
        let session = await DiagnosticsClient.shared.getCurrentSession()
        XCTAssertNil(session)
    }
    
    // MARK: - Breadcrumb Tests
    
    func testBreadcrumbAdding() async {
        let config = DiagnosticsConfiguration(
            productId: "test-app",
            anonymousInstallId: "install-123",
            platform: "ios",
            channel: "ios_app",
            osFamily: "ios",
            appVersion: "1.0.0",
            buildNumber: "100",
            releaseChannel: "beta",
            serverUrl: "https://api.test.com"
        )
        
        await DiagnosticsClient.shared.configure(config)
        
        await DiagnosticsClient.shared.breadcrumb(
            category: "navigation",
            message: "Page loaded",
            data: ["path": AnyCodable("/home")]
        )
        
        let breadcrumbs = await DiagnosticsClient.shared.getBreadcrumbs()
        XCTAssertEqual(breadcrumbs.count, 1)
        XCTAssertEqual(breadcrumbs.first?.category, "navigation")
        XCTAssertEqual(breadcrumbs.first?.message, "Page loaded")
    }
    
    func testMultipleBreadcrumbs() async {
        let config = DiagnosticsConfiguration(
            productId: "test-app",
            anonymousInstallId: "install-123",
            platform: "ios",
            channel: "ios_app",
            osFamily: "ios",
            appVersion: "1.0.0",
            buildNumber: "100",
            releaseChannel: "beta",
            serverUrl: "https://api.test.com"
        )
        
        await DiagnosticsClient.shared.configure(config)
        
        for i in 1...5 {
            await DiagnosticsClient.shared.breadcrumb(
                category: "test",
                message: "Message \(i)"
            )
        }
        
        let breadcrumbs = await DiagnosticsClient.shared.getBreadcrumbs()
        XCTAssertEqual(breadcrumbs.count, 5)
    }
    
    // MARK: - Tracing Tests
    
    func testSuccessfulTrace() async throws {
        let config = DiagnosticsConfiguration(
            productId: "test-app",
            anonymousInstallId: "install-123",
            platform: "ios",
            channel: "ios_app",
            osFamily: "ios",
            appVersion: "1.0.0",
            buildNumber: "100",
            releaseChannel: "beta",
            serverUrl: "https://api.test.com"
        )
        
        await DiagnosticsClient.shared.configure(config)
        
        let result = try await DiagnosticsClient.shared.trace(name: "test-operation") {
            return 42
        }
        
        XCTAssertEqual(result, 42)
    }
    
    func testFailingTrace() async {
        let config = DiagnosticsConfiguration(
            productId: "test-app",
            anonymousInstallId: "install-123",
            platform: "ios",
            channel: "ios_app",
            osFamily: "ios",
            appVersion: "1.0.0",
            buildNumber: "100",
            releaseChannel: "beta",
            serverUrl: "https://api.test.com"
        )
        
        await DiagnosticsClient.shared.configure(config)
        
        do {
            _ = try await DiagnosticsClient.shared.trace(name: "failing-operation") {
                throw TestError.test
            }
            XCTFail("Should have thrown")
        } catch {
            // Expected
            XCTAssertTrue(error is TestError)
        }
    }
    
    // MARK: - Logging Tests
    
    func testLogAdding() async {
        let config = DiagnosticsConfiguration(
            productId: "test-app",
            anonymousInstallId: "install-123",
            platform: "ios",
            channel: "ios_app",
            osFamily: "ios",
            appVersion: "1.0.0",
            buildNumber: "100",
            releaseChannel: "beta",
            serverUrl: "https://api.test.com"
        )
        
        await DiagnosticsClient.shared.configure(config)
        
        await DiagnosticsClient.shared.log(
            level: .info,
            message: "Test message",
            module: "TestModule"
        )
        
        // Log is buffered, no immediate assertion possible
        // But breadcrumb should be created
        let breadcrumbs = await DiagnosticsClient.shared.getBreadcrumbs()
        XCTAssertTrue(breadcrumbs.contains { $0.category == "log" })
    }
    
    // MARK: - Types Tests
    
    func testDiagnosticsSessionEncoding() throws {
        let session = DiagnosticsSession(
            id: "ds_test123",
            productId: "test-app",
            status: .active,
            collectionLevel: .debug,
            captureLogs: true,
            captureNetwork: true,
            captureScreenshots: false,
            screenshotOnError: true,
            maxDurationMinutes: 60,
            createdAt: "2026-03-03T12:00:00Z",
            expiresAt: "2026-03-03T13:00:00Z"
        )
        
        let encoder = JSONEncoder()
        let data = try encoder.encode(session)
        
        let decoder = JSONDecoder()
        let decoded = try decoder.decode(DiagnosticsSession.self, from: data)
        
        XCTAssertEqual(decoded.id, session.id)
        XCTAssertEqual(decoded.status, session.status)
        XCTAssertEqual(decoded.collectionLevel, session.collectionLevel)
    }
    
    func testLogEntryCreation() {
        let entry = DiagnosticsLogEntry(
            level: .error,
            message: "Something went wrong",
            timestamp: "2026-03-03T12:00:00Z",
            module: "TestModule",
            file: "Test.swift",
            line: 42,
            function: "testFunction",
            context: ["key": AnyCodable("value")],
            correlationId: "corr-123"
        )
        
        XCTAssertEqual(entry.level, .error)
        XCTAssertEqual(entry.message, "Something went wrong")
        XCTAssertEqual(entry.module, "TestModule")
        XCTAssertEqual(entry.line, 42)
        XCTAssertEqual(entry.correlationId, "corr-123")
    }
    
    func testTraceSpanCreation() {
        let span = DiagnosticsTraceSpan(
            spanId: "span-123",
            parentId: "parent-456",
            name: "test-span",
            kind: .internal,
            startTime: "2026-03-03T12:00:00Z",
            endTime: "2026-03-03T12:00:01Z",
            durationMs: 1000,
            attributes: ["key": AnyCodable("value")],
            status: .ok,
            statusMessage: nil
        )
        
        XCTAssertEqual(span.spanId, "span-123")
        XCTAssertEqual(span.parentId, "parent-456")
        XCTAssertEqual(span.name, "test-span")
        XCTAssertEqual(span.status, .ok)
    }
    
    func testBreadcrumbCreation() {
        let breadcrumb = DiagnosticsBreadcrumb(
            timestamp: "2026-03-03T12:00:00Z",
            category: "navigation",
            message: "User tapped button",
            data: ["buttonId": AnyCodable("submit")]
        )
        
        XCTAssertEqual(breadcrumb.category, "navigation")
        XCTAssertEqual(breadcrumb.message, "User tapped button")
        XCTAssertNotNil(breadcrumb.data)
    }
    
    func testAnyCodableEncoding() throws {
        let value = AnyCodable("test-string")
        let encoder = JSONEncoder()
        let data = try encoder.encode(value)
        
        // Should not throw
        XCTAssertFalse(data.isEmpty)
    }
    
    func testAnyCodableIntEncoding() throws {
        let value = AnyCodable(42)
        let encoder = JSONEncoder()
        let data = try encoder.encode(value)
        
        XCTAssertFalse(data.isEmpty)
    }
    
    func testAnyCodableBoolEncoding() throws {
        let value = AnyCodable(true)
        let encoder = JSONEncoder()
        let data = try encoder.encode(value)
        
        XCTAssertFalse(data.isEmpty)
    }
}

enum TestError: Error {
    case test
}
