import Foundation
@testable import peekaboo
import Testing

@Suite("JSONOutput Tests", .tags(.jsonOutput, .unit))
struct JSONOutputTests {
    // MARK: - AnyCodable Tests

    @Test("AnyCodable encoding with various types", .tags(.fast))
    func anyCodableEncodingVariousTypes() throws {
        // Test string
        let stringValue = AnyCodable("test string")
        let stringData = try JSONEncoder().encode(stringValue)
        let stringResult = try JSONSerialization.jsonObject(with: stringData) as? String
        #expect(stringResult == "test string")

        // Test number
        let numberValue = AnyCodable(42)
        let numberData = try JSONEncoder().encode(numberValue)
        let numberResult = try JSONSerialization.jsonObject(with: numberData) as? Int
        #expect(numberResult == 42)

        // Test boolean
        let boolValue = AnyCodable(true)
        let boolData = try JSONEncoder().encode(boolValue)
        let boolResult = try JSONSerialization.jsonObject(with: boolData) as? Bool
        #expect(boolResult == true)

        // Test null (using optional nil)
        let nilValue: String? = nil
        let nilAnyCodable = AnyCodable(nilValue as Any)
        let nilData = try JSONEncoder().encode(nilAnyCodable)
        let nilString = String(data: nilData, encoding: .utf8)
        #expect(nilString == "null")
    }

    @Test("AnyCodable with nested structures", .tags(.fast))
    func anyCodableNestedStructures() throws {
        // Test array
        let arrayValue = AnyCodable([1, 2, 3])
        let arrayData = try JSONEncoder().encode(arrayValue)
        let arrayResult = try JSONSerialization.jsonObject(with: arrayData) as? [Int]
        #expect(arrayResult == [1, 2, 3])

        // Test dictionary
        let dictValue = AnyCodable(["key": "value", "number": 42])
        let dictData = try JSONEncoder().encode(dictValue)
        let dictResult = try JSONSerialization.jsonObject(with: dictData) as? [String: Any]
        #expect(dictResult?["key"] as? String == "value")
        #expect(dictResult?["number"] as? Int == 42)
    }

    @Test("AnyCodable decoding", .tags(.fast))
    func anyCodableDecoding() throws {
        // Test decoding from JSON
        let jsonString = #"{"string": "test", "number": 42, "bool": true, "null": null}"#
        let jsonData = Data(jsonString.utf8)
        let decoded = try JSONDecoder().decode([String: AnyCodable].self, from: jsonData)

        #expect(decoded["string"]?.value as? String == "test")
        #expect(decoded["number"]?.value as? Int == 42)
        #expect(decoded["bool"]?.value as? Bool == true)
        #expect(decoded["null"]?.value == nil)
    }

    // MARK: - AnyEncodable Tests

    @Test("AnyEncodable with custom types", .tags(.fast))
    func anyEncodableCustomTypes() throws {
        // Test with ApplicationInfo
        let appInfo = ApplicationInfo(
            app_name: "Test App",
            bundle_id: "com.test.app",
            pid: 1234,
            is_active: true,
            window_count: 2
        )

        // Test encoding through AnyCodable instead
        let anyCodable = AnyCodable(appInfo)
        let data = try JSONEncoder().encode(anyCodable)
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]

        #expect(json?["app_name"] as? String == "Test App")
        #expect(json?["bundle_id"] as? String == "com.test.app")
        #expect(json?["pid"] as? Int32 == 1234)
        #expect(json?["is_active"] as? Bool == true)
        #expect(json?["window_count"] as? Int == 2)
    }

    // MARK: - JSON Output Function Tests

    @Test("outputJSON function with success data", .tags(.fast))
    func outputJSONSuccess() throws {
        // Test data
        let testData = ApplicationListData(applications: [
            ApplicationInfo(
                app_name: "Finder",
                bundle_id: "com.apple.finder",
                pid: 123,
                is_active: true,
                window_count: 1
            )
        ])

        // Test JSON serialization directly without capturing stdout
        let encoder = JSONEncoder()
        let data = try encoder.encode(testData)
        let jsonString = String(data: data, encoding: .utf8) ?? ""

        // Verify JSON structure
        #expect(jsonString.contains("Finder"))
        #expect(jsonString.contains("com.apple.finder"))
        #expect(!jsonString.isEmpty)
    }

    @Test("CodableJSONResponse structure", .tags(.fast))
    func codableJSONResponseStructure() throws {
        let testData = ["test": "value"]
        let response = CodableJSONResponse(
            success: true,
            data: testData,
            messages: nil,
            debug_logs: []
        )

        let encoder = JSONEncoder()
        let data = try encoder.encode(response)
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]

        #expect(json?["success"] as? Bool == true)
        #expect((json?["data"] as? [String: Any])?["test"] as? String == "value")
        #expect(json?["error"] == nil)
    }

    @Test("Error output JSON formatting", .tags(.fast))
    func errorOutputJSONFormatting() throws {
        // Test error JSON structure directly
        let errorInfo = ErrorInfo(
            message: "Test error message",
            code: .APP_NOT_FOUND,
            details: "Additional error details"
        )

        let response = JSONResponse(
            success: false,
            data: nil,
            messages: nil,
            error: errorInfo
        )

        let encoder = JSONEncoder()
        let data = try encoder.encode(response)
        let jsonString = String(data: data, encoding: .utf8) ?? ""

        // Verify error JSON structure
        #expect(jsonString.contains("\"success\":false") || jsonString.contains("\"success\": false"))
        #expect(jsonString.contains("\"error\""))
        #expect(jsonString.contains("Test error message"))
        #expect(jsonString.contains("APP_NOT_FOUND"))
    }

    // MARK: - Edge Cases and Error Handling

    @Test("AnyCodable with complex nested data", .tags(.fast))
    func anyCodableComplexNestedData() throws {
        let complexData: [String: Any] = [
            "simple": "string",
            "nested": [
                "array": [1, 2, 3],
                "dict": ["key": "value"],
                "mixed": [
                    "string",
                    42,
                    true,
                    ["nested": "array"]
                ]
            ]
        ]

        let anyCodable = AnyCodable(complexData)
        let encoded = try JSONEncoder().encode(anyCodable)
        let decoded = try JSONSerialization.jsonObject(with: encoded) as? [String: Any]

        #expect(decoded?["simple"] as? String == "string")
        #expect((decoded?["nested"] as? [String: Any]) != nil)
    }

    @Test("JSON encoding performance with large data", .tags(.performance))
    func jsonEncodingPerformance() throws {
        // Create large dataset
        var largeAppList: [ApplicationInfo] = []
        for index in 0..<100 {
            let appInfo = ApplicationInfo(
                app_name: "App \(index)",
                bundle_id: "com.test.app\(index)",
                pid: Int32(1000 + index),
                is_active: index.isMultiple(of: 2),
                window_count: index % 10
            )
            largeAppList.append(appInfo)
        }

        let data = ApplicationListData(applications: largeAppList)

        // Measure encoding performance
        let startTime = CFAbsoluteTimeGetCurrent()
        let encoded = try JSONEncoder().encode(data)
        let encodingTime = CFAbsoluteTimeGetCurrent() - startTime

        #expect(!encoded.isEmpty)
        #expect(encodingTime < 1.0) // Should encode within 1 second
    }

    @Test("Thread safety of JSON operations", .tags(.concurrency))
    func threadSafetyJSONOperations() async {
        await withTaskGroup(of: Bool.self) { group in
            for index in 0..<10 {
                group.addTask {
                    do {
                        let appInfo = ApplicationInfo(
                            app_name: "App \(index)",
                            bundle_id: "com.test.app\(index)",
                            pid: Int32(1000 + index),
                            is_active: true,
                            window_count: 1
                        )

                        // Test encoding through AnyCodable instead
                        let anyCodable = AnyCodable(appInfo)
                        _ = try JSONEncoder().encode(anyCodable)
                        return true
                    } catch {
                        return false
                    }
                }
            }

            var successCount = 0
            for await success in group where success {
                successCount += 1
            }

            #expect(successCount == 10)
        }
    }

    @Test("Memory usage with repeated JSON operations", .tags(.memory))
    func memoryUsageJSONOperations() {
        // Test memory doesn't grow excessively with repeated JSON operations
        for _ in 1...100 {
            let data = ApplicationInfo(
                app_name: "Test",
                bundle_id: "com.test",
                pid: 123,
                is_active: true,
                window_count: 1
            )

            do {
                let encoded = try JSONEncoder().encode(data)
                #expect(!encoded.isEmpty)
            } catch {
                Issue.record("JSON encoding should not fail: \(error)")
            }
        }
    }

    @Test("Error code enum completeness", .tags(.fast))
    func errorCodeEnumCompleteness() {
        // Test that all error codes have proper raw values
        let errorCodes: [ErrorCode] = [
            .PERMISSION_ERROR_SCREEN_RECORDING,
            .PERMISSION_ERROR_ACCESSIBILITY,
            .APP_NOT_FOUND,
            .AMBIGUOUS_APP_IDENTIFIER,
            .WINDOW_NOT_FOUND,
            .CAPTURE_FAILED,
            .FILE_IO_ERROR,
            .INVALID_ARGUMENT,
            .UNKNOWN_ERROR
        ]

        for errorCode in errorCodes {
            #expect(!errorCode.rawValue.isEmpty)
            let isAllASCII = errorCode.rawValue.allSatisfy { character in
                character.isASCII
            }
            #expect(isAllASCII)
        }
    }
}

// MARK: - Extension Test Suite for Output Format Validation

@Suite("JSON Output Format Validation", .tags(.jsonOutput, .integration))
struct JSONOutputFormatValidationTests {
    @Test("MCP protocol compliance", .tags(.integration))
    func mcpProtocolCompliance() throws {
        // Test that JSON output follows MCP protocol format
        let testData = ApplicationListData(applications: [])
        let response = CodableJSONResponse(
            success: true,
            data: testData,
            messages: nil,
            debug_logs: []
        )

        let encoder = JSONEncoder()
        // Properties are already in snake_case, no conversion needed
        let data = try encoder.encode(response)

        // Verify it's valid JSON
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        #expect(json != nil) // JSON was successfully created

        // Verify required MCP fields
        #expect(json?["success"] != nil)
        #expect(json?["data"] != nil)
    }

    @Test("Snake case conversion consistency", .tags(.fast))
    func snakeCaseConversionConsistency() throws {
        let appInfo = ApplicationInfo(
            app_name: "Test App",
            bundle_id: "com.test.app",
            pid: 1234,
            is_active: true,
            window_count: 2
        )

        let encoder = JSONEncoder()
        // Properties are already in snake_case, no conversion needed
        let data = try encoder.encode(appInfo)
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]

        // Verify snake_case conversion
        #expect(json?["app_name"] != nil)
        #expect(json?["bundle_id"] != nil)
        #expect(json?["is_active"] != nil)
        #expect(json?["window_count"] != nil)

        // Verify no camelCase keys exist
        #expect(json?["appName"] == nil)
        #expect(json?["bundleId"] == nil)
        #expect(json?["isActive"] == nil)
        #expect(json?["windowCount"] == nil)
    }

    @Test("Large data structure serialization", .tags(.performance))
    func largeDataStructureSerialization() throws {
        // Create a complex data structure
        var windows: [WindowInfo] = []
        for index in 0..<100 {
            let window = WindowInfo(
                window_title: "Window \(index)",
                window_id: UInt32(1000 + index),
                window_index: index,
                bounds: WindowBounds(xCoordinate: index * 10, yCoordinate: index * 10, width: 800, height: 600),
                is_on_screen: index.isMultiple(of: 2)
            )
            windows.append(window)
        }

        let windowData = WindowListData(
            windows: windows,
            target_application_info: TargetApplicationInfo(
                app_name: "Test App",
                bundle_id: "com.test.app",
                pid: 1234
            )
        )

        let startTime = CFAbsoluteTimeGetCurrent()
        let encoded = try JSONEncoder().encode(windowData)
        let duration = CFAbsoluteTimeGetCurrent() - startTime

        #expect(!encoded.isEmpty)
        #expect(duration < 0.5) // Should complete within 500ms

        // Verify the JSON is valid
        _ = try JSONSerialization.jsonObject(with: encoded)
        #expect(Bool(true)) // JSON was successfully created
    }
}
