import Foundation
import Testing

@Suite("API Error Handling Tests", .tags(.critical, .networking))
struct APIErrorTests {
    // MARK: - Network Error Scenarios

    @Test("Network timeout error handling")
    func networkTimeout() {
        enum APIError: Error, Equatable {
            case networkError(URLError)
            case noServerConfigured

            var localizedDescription: String {
                switch self {
                case .networkError(let urlError):
                    switch urlError.code {
                    case .timedOut:
                        "Connection timed out"
                    case .notConnectedToInternet:
                        "No internet connection"
                    case .cannotFindHost:
                        "Cannot find server - check the address"
                    case .cannotConnectToHost:
                        "Cannot connect to server - is it running?"
                    case .networkConnectionLost:
                        "Network connection lost"
                    default:
                        urlError.localizedDescription
                    }
                case .noServerConfigured:
                    "No server configured"
                }
            }
        }

        let timeoutError = APIError.networkError(URLError(.timedOut))
        #expect(timeoutError.localizedDescription == "Connection timed out")

        let noInternetError = APIError.networkError(URLError(.notConnectedToInternet))
        #expect(noInternetError.localizedDescription == "No internet connection")

        let hostNotFoundError = APIError.networkError(URLError(.cannotFindHost))
        #expect(hostNotFoundError.localizedDescription == "Cannot find server - check the address")
    }

    @Test("HTTP status code error mapping")
    func hTTPStatusErrors() {
        struct ServerError {
            let code: Int
            let message: String?

            var description: String {
                if let message {
                    return message
                }
                switch code {
                case 400: return "Bad request - check your input"
                case 401: return "Unauthorized - authentication required"
                case 403: return "Forbidden - access denied"
                case 404: return "Not found - endpoint doesn't exist"
                case 409: return "Conflict - resource already exists"
                case 422: return "Unprocessable entity - validation failed"
                case 429: return "Too many requests - rate limit exceeded"
                case 500: return "Server error - internal server error"
                case 502: return "Bad gateway - server is down"
                case 503: return "Service unavailable"
                default: return "Server error: \(code)"
                }
            }
        }

        // Test common HTTP errors
        #expect(ServerError(code: 400, message: nil).description == "Bad request - check your input")
        #expect(ServerError(code: 401, message: nil).description == "Unauthorized - authentication required")
        #expect(ServerError(code: 404, message: nil).description == "Not found - endpoint doesn't exist")
        #expect(ServerError(code: 429, message: nil).description == "Too many requests - rate limit exceeded")
        #expect(ServerError(code: 500, message: nil).description == "Server error - internal server error")

        // Test custom error message takes precedence
        #expect(ServerError(code: 404, message: "Session not found").description == "Session not found")

        // Test unknown status code
        #expect(ServerError(code: 418, message: nil).description == "Server error: 418")
    }

    @Test("Error response body parsing")
    func errorResponseParsing() throws {
        // Standard error format
        struct ErrorResponse: Codable {
            let error: String?
            let message: String?
            let details: String?
            let code: String?
        }

        // Test various error response formats
        let errorFormats = [
            // Format 1: Simple error field
            """
            {"error": "Invalid session ID"}
            """,
            // Format 2: Message field
            """
            {"message": "Authentication failed", "code": "AUTH_FAILED"}
            """,
            // Format 3: Detailed error
            """
            {"error": "Validation error", "details": "Field 'command' is required"}
            """,
            // Format 4: All fields
            """
            {"error": "Request failed", "message": "Invalid input", "details": "Missing required fields", "code": "VALIDATION_ERROR"}
            """
        ]

        for json in errorFormats {
            let data = json.data(using: .utf8)!
            let response = try JSONDecoder().decode(ErrorResponse.self, from: data)

            // Verify at least one error field is present
            let hasError = response.error != nil || response.message != nil || response.details != nil
            #expect(hasError == true)
        }
    }

    // MARK: - Decoding Error Scenarios

    @Test("Invalid JSON response handling")
    func invalidJSONResponse() {
        let invalidResponses = [
            "", // Empty response
            "not json", // Plain text
            "{invalid json}", // Malformed JSON
            "null", // Null response
            "undefined", // JavaScript undefined
            "<html>404 Not Found</html>" // HTML error page
        ]

        for response in invalidResponses {
            let data = response.data(using: .utf8) ?? Data()

            // Attempt to decode as array of sessions
            struct Session: Codable {
                let id: String
                let command: String
            }

            do {
                _ = try JSONDecoder().decode([Session].self, from: data)
                Issue.record("Should have thrown decoding error for: \(response)")
            } catch {
                // Expected to fail
                #expect(error is DecodingError)
            }
        }
    }

    @Test("Partial JSON response handling")
    func partialJSONResponse() throws {
        // Session with missing required fields
        let partialSession = """
        {
            "id": "test-123"
        }
        """

        struct Session: Codable {
            let id: String
            let command: String
            let workingDir: String
            let status: String
            let startedAt: String
        }

        let data = partialSession.data(using: .utf8)!

        #expect(throws: DecodingError.self) {
            try JSONDecoder().decode(Session.self, from: data)
        }
    }

    // MARK: - Request Validation

    @Test("Invalid request parameters")
    func invalidRequestParameters() {
        // Test session creation with invalid data
        struct SessionCreateRequest {
            let command: [String]
            let workingDir: String
            let cols: Int?
            let rows: Int?

            func validate() -> String? {
                if command.isEmpty {
                    return "Command cannot be empty"
                }
                if command.first?.isEmpty == true {
                    return "Command cannot be empty string"
                }
                if workingDir.isEmpty {
                    return "Working directory cannot be empty"
                }
                if let cols, cols <= 0 {
                    return "Terminal width must be positive"
                }
                if let rows, rows <= 0 {
                    return "Terminal height must be positive"
                }
                return nil
            }
        }

        // Test various invalid inputs
        let invalidRequests = [
            SessionCreateRequest(command: [], workingDir: "/tmp", cols: 80, rows: 24),
            SessionCreateRequest(command: [""], workingDir: "/tmp", cols: 80, rows: 24),
            SessionCreateRequest(command: ["bash"], workingDir: "", cols: 80, rows: 24),
            SessionCreateRequest(command: ["bash"], workingDir: "/tmp", cols: 0, rows: 24),
            SessionCreateRequest(command: ["bash"], workingDir: "/tmp", cols: 80, rows: -1)
        ]

        for request in invalidRequests {
            #expect(request.validate() != nil)
        }

        // Valid request should pass
        let validRequest = SessionCreateRequest(command: ["bash"], workingDir: "/tmp", cols: 80, rows: 24)
        #expect(validRequest.validate() == nil)
    }

    // MARK: - Connection State Errors

    @Test("No server configured error")
    func noServerConfiguredError() {
        enum APIError: Error {
            case noServerConfigured
            case invalidURL

            var localizedDescription: String {
                switch self {
                case .noServerConfigured:
                    "No server configured. Please connect to a server first."
                case .invalidURL:
                    "Invalid server URL"
                }
            }
        }

        let error = APIError.noServerConfigured
        #expect(error.localizedDescription.contains("No server configured"))
    }

    @Test("Empty response handling")
    func emptyResponseHandling() throws {
        // Some endpoints return 204 No Content
        let emptyData = Data()

        // For endpoints that should return data
        struct SessionListResponse: Codable {
            let sessions: [Session]

            struct Session: Codable {
                let id: String
            }
        }

        // Empty data should throw when expecting content
        #expect(throws: DecodingError.self) {
            try JSONDecoder().decode(SessionListResponse.self, from: emptyData)
        }

        // But empty array is valid
        let emptyArrayData = "[]".data(using: .utf8)!
        let sessions = try JSONDecoder().decode([SessionListResponse.Session].self, from: emptyArrayData)
        #expect(sessions.isEmpty)
    }

    // MARK: - Retry Logic

    @Test("Retry behavior for transient errors")
    func retryLogic() {
        struct RetryPolicy {
            let maxAttempts: Int
            let retryableErrors: Set<Int>

            func shouldRetry(attempt: Int, statusCode: Int) -> Bool {
                attempt < maxAttempts && retryableErrors.contains(statusCode)
            }

            func delayForAttempt(_ attempt: Int) -> TimeInterval {
                // Exponential backoff: 1s, 2s, 4s, 8s...
                pow(2.0, Double(attempt - 1))
            }
        }

        let policy = RetryPolicy(
            maxAttempts: 3,
            retryableErrors: [408, 429, 502, 503, 504] // Timeout, rate limit, gateway errors
        )

        // Should retry on retryable errors
        #expect(policy.shouldRetry(attempt: 1, statusCode: 503) == true)
        #expect(policy.shouldRetry(attempt: 2, statusCode: 429) == true)

        // Should not retry on non-retryable errors
        #expect(policy.shouldRetry(attempt: 1, statusCode: 404) == false)
        #expect(policy.shouldRetry(attempt: 1, statusCode: 401) == false)

        // Should stop after max attempts
        #expect(policy.shouldRetry(attempt: 3, statusCode: 503) == false)

        // Test backoff delays
        #expect(policy.delayForAttempt(1) == 1.0)
        #expect(policy.delayForAttempt(2) == 2.0)
        #expect(policy.delayForAttempt(3) == 4.0)
    }

    // MARK: - Edge Cases

    @Test("Unicode and special characters in errors")
    func unicodeErrorMessages() throws {
        let errorMessages = [
            "Error: File not found 文件未找到",
            "❌ Operation failed",
            "Error: Path contains invalid characters: /tmp/test-file",
            "Session 'test—session' not found", // em dash
            "Invalid input: 🚫"
        ]

        struct ErrorResponse: Codable {
            let error: String
        }

        for message in errorMessages {
            let json = """
            {"error": "\(message.replacingOccurrences(of: "\"", with: "\\\""))"}
            """

            let data = json.data(using: .utf8)!
            let response = try JSONDecoder().decode(ErrorResponse.self, from: data)
            #expect(response.error == message)
        }
    }

    @Test("Concurrent error handling")
    func concurrentErrors() async {
        // Simulate multiple concurrent API calls failing
        actor ErrorCollector {
            private var errors: [String] = []

            func addError(_ error: String) {
                errors.append(error)
            }

            func getErrors() -> [String] {
                errors
            }
        }

        let collector = ErrorCollector()

        // Simulate concurrent operations
        await withTaskGroup(of: Void.self) { group in
            for i in 1...5 {
                group.addTask {
                    // Simulate API call and error
                    let error = "Error from request \(i)"
                    await collector.addError(error)
                }
            }
        }

        let errors = await collector.getErrors()
        #expect(errors.count == 5)
    }
}
