//
// Created by Banghua Zhao on 09/07/2025
// Copyright Apps Bay Limited. All rights reserved.
//

import Foundation

// MARK: - Gemini API Models

struct GeminiRequest: Codable {
    let contents: [GeminiContent]
    
    init(prompt: String) {
        self.contents = [GeminiContent(parts: [GeminiPart(text: prompt)])]
    }
}

struct GeminiContent: Codable {
    let parts: [GeminiPart]
}

struct GeminiPart: Codable {
    let text: String
}

struct GeminiResponse: Codable {
    let candidates: [GeminiCandidate]?
    let promptFeedback: PromptFeedback?
}

struct GeminiCandidate: Codable {
    let content: GeminiContent
    let finishReason: String?
    let index: Int?
    let safetyRatings: [SafetyRating]?
}

struct PromptFeedback: Codable {
    let safetyRatings: [SafetyRating]?
}

struct SafetyRating: Codable {
    let category: String?
    let probability: String?
}

// MARK: - Gemini Service

class GeminiService {
    // MARK: - Configuration
    
    private let apiKey: String
    private let baseURL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
    
    // MARK: - Initialization
    
    init(apiKey: String? = nil) {
        self.apiKey = apiKey ?? Config.geminiAPIKey
        
        if self.apiKey.isEmpty {
            print("⚠️ Warning: No Gemini API key found. Set GEMINI_API_KEY environment variable or create config.plist file.")
        } else {
            print("✅ Gemini API key configured")
        }
    }
    
    // MARK: - Public Methods
    
    /// Generate text using Gemini API with context and query
    func generate(prompt: String, query: String) async throws -> String {
        // Check if API key is available
        guard !apiKey.isEmpty else {
            throw GeminiError.noAPIKey
        }
        
        return try await callAPI(prompt: prompt)
    }
    
    /// Generate text with custom prompt
    func generate(prompt: String) async throws -> String {
        // Check if API key is available
        guard !apiKey.isEmpty else {
            throw GeminiError.noAPIKey
        }
        
        return try await callAPI(prompt: prompt)
    }
    
    // MARK: - Private Methods
    
    private func callAPI(prompt: String) async throws -> String {
        guard let url = URL(string: baseURL) else {
            throw GeminiError.invalidURL
        }
        
        let request = GeminiRequest(prompt: prompt)
        
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        urlRequest.setValue(apiKey, forHTTPHeaderField: "X-goog-api-key")
        urlRequest.httpBody = try JSONEncoder().encode(request)
        
        let (data, response) = try await URLSession.shared.data(for: urlRequest)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw GeminiError.invalidResponse
        }
        
        guard httpResponse.statusCode == 200 else {
            let errorString = String(data: data, encoding: .utf8) ?? "Unknown error"
            throw GeminiError.apiError(httpResponse.statusCode, errorString)
        }
        
        let geminiResponse = try JSONDecoder().decode(GeminiResponse.self, from: data)
        
        guard let candidate = geminiResponse.candidates?.first,
              let text = candidate.content.parts.first?.text else {
            throw GeminiError.noResponseText
        }
        
        return text
    }
}

// MARK: - Gemini Errors

enum GeminiError: LocalizedError {
    case noAPIKey
    case invalidURL
    case invalidResponse
    case apiError(Int, String)
    case noResponseText
    
    var errorDescription: String? {
        switch self {
        case .noAPIKey:
            return "Gemini API key not configured"
        case .invalidURL:
            return "Invalid API URL"
        case .invalidResponse:
            return "Invalid response from API"
        case .apiError(let code, let message):
            return "API Error (\(code)): \(message)"
        case .noResponseText:
            return "No response text received from API"
        }
    }
} 
