import Foundation

// MARK: - Configuration

struct AIConfig: Decodable {
    let baseURL:            String
    let completionsPath:    String  // z.B. "/v1/chat/completions" oder "/api/chat/completions"
    let apiKey:             String
    let model:              String
    let timeoutSeconds:     Double
    let rateLimitPerHour:   Int
    let systemPrompt:        String
    let userPromptTemplate:  String
    let giftPromptTemplate:  String

    var completionsURL: String { baseURL + completionsPath }

    // Fallback-Werte falls die JSON-Datei nicht geladen werden kann
    static let fallback = AIConfig(
        baseURL:            "https://api.mistral.ai",
        completionsPath:    "/v1/chat/completions",
        apiKey:             "zyXgjAmQl8WwVV0qoe8rSNajqrwK6QXQ",
        model:              "mistral-small-latest",
        timeoutSeconds:     60,
        rateLimitPerHour:   3,
        systemPrompt:       "Du bist ein einfühlsamer Assistent für persönliche Beziehungspflege. Antworte ausschließlich auf Deutsch. Sei prägnant, warm und direkt. Strukturiere deine Antwort exakt wie verlangt.",
        userPromptTemplate: "Person: {{personName}}\n{{birthday}}{{interests}}\n{{moments}}\n{{logEntries}}\nAnalysiere diese Beziehung und antworte exakt in diesem Format:\n\nMUSTER: [2-3 Sätze]\nBEZIEHUNG: [2-3 Sätze]\nEMPFEHLUNG: [1 konkreter Schritt]",
        giftPromptTemplate: "Person: {{personName}}\n{{birthday}}{{interests}}\n{{moments}}\nSchlage 3 Geschenkideen vor. Antworte exakt in diesem Format:\n\nIDEE 1: [Idee – Begründung]\nIDEE 2: [Idee – Begründung]\nIDEE 3: [Idee – Begründung]"
    )

    static func load() -> AIConfig {
        // Laufzeit-Overrides aus UserDefaults haben höchste Priorität
        let ud = UserDefaults.standard
        let base = loadFromBundle()
        return AIConfig(
            baseURL:            ud.string(forKey: "aiBaseURL") ?? base.baseURL,
            completionsPath:    base.completionsPath,
            apiKey:             ud.string(forKey: "aiAPIKey")  ?? base.apiKey,
            model:              ud.string(forKey: "aiModel")   ?? base.model,
            timeoutSeconds:     base.timeoutSeconds,
            rateLimitPerHour:   base.rateLimitPerHour,
            systemPrompt:       base.systemPrompt,
            userPromptTemplate: base.userPromptTemplate,
            giftPromptTemplate: base.giftPromptTemplate
        )
    }

    private static func loadFromBundle() -> AIConfig {
        guard
            let url  = Bundle.main.url(forResource: "AIConfiguration", withExtension: "json"),
            let data = try? Data(contentsOf: url)
        else { return .fallback }

        let decoder = JSONDecoder()
        return (try? decoder.decode(AIConfig.self, from: data)) ?? .fallback
    }
}

// MARK: - Analysis Result

struct AIAnalysisResult {
    let patterns:       String
    let relationship:   String
    let recommendation: String
}

// MARK: - Cached Analysis

struct CachedAnalysis: Codable {
    let patterns:       String
    let relationship:   String
    let recommendation: String
    let analyzedAt:     Date

    var asResult: AIAnalysisResult {
        AIAnalysisResult(patterns: patterns, relationship: relationship, recommendation: recommendation)
    }

    init(result: AIAnalysisResult, date: Date = Date()) {
        self.patterns       = result.patterns
        self.relationship   = result.relationship
        self.recommendation = result.recommendation
        self.analyzedAt     = date
    }
}

// MARK: - Cached Gift Suggestion

struct CachedGiftSuggestion: Codable {
    let text: String
    let generatedAt: Date
}

// MARK: - Service

class AIAnalysisService {
    static let shared = AIAnalysisService()
    private init() {}

    // MARK: - Rate Limiting (max 3 pro Stunde)

    private var maxRequestsPerHour: Int { AIConfig.load().rateLimitPerHour }
    private let rateLimitKey = "ai_rate_timestamps"

    var remainingRequests: Int {
        let recent = recentTimestamps()
        return max(0, maxRequestsPerHour - recent.count)
    }

    var isRateLimited: Bool { remainingRequests == 0 }

    var rateLimitResetsAt: Date? {
        let timestamps = recentTimestamps()
        guard timestamps.count >= maxRequestsPerHour,
              let oldest = timestamps.min()
        else { return nil }
        return Date(timeIntervalSince1970: oldest + 3600)
    }

    private func recentTimestamps() -> [Double] {
        let all = UserDefaults.standard.array(forKey: rateLimitKey) as? [Double] ?? []
        let cutoff = Date().timeIntervalSince1970 - 3600
        return all.filter { $0 > cutoff }
    }

    private func recordRequest() {
        var recent = recentTimestamps()
        recent.append(Date().timeIntervalSince1970)
        UserDefaults.standard.set(recent, forKey: rateLimitKey)
    }

    // MARK: - Cache

    private func cacheKey(for person: Person) -> String { "ai_cache_\(person.id.uuidString)" }

    func loadCached(for person: Person) -> CachedAnalysis? {
        guard let data = UserDefaults.standard.data(forKey: cacheKey(for: person)),
              let cached = try? JSONDecoder().decode(CachedAnalysis.self, from: data)
        else { return nil }
        return cached
    }

    private func saveCache(_ result: AIAnalysisResult, for person: Person) {
        let cached = CachedAnalysis(result: result)
        if let data = try? JSONEncoder().encode(cached) {
            UserDefaults.standard.set(data, forKey: cacheKey(for: person))
        }
    }

    // MARK: - Analyze

    func analyze(person: Person) async throws -> AIAnalysisResult {
        let config = AIConfig.load()

        guard let url = URL(string: config.completionsURL) else {
            throw URLError(.badURL)
        }

        let prompt = buildPrompt(for: person, template: config.userPromptTemplate)

        let body: [String: Any] = [
            "model":   config.model,
            "stream":  false,
            "messages": [
                ["role": "system", "content": config.systemPrompt],
                ["role": "user",   "content": prompt]
            ]
        ]

        var request = URLRequest(url: url, timeoutInterval: config.timeoutSeconds)
        request.httpMethod = "POST"
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json",         forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONSerialization.data(withJSONObject: body)

        let (data, response) = try await URLSession.shared.data(for: request)

        guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }

        guard
            let json    = try JSONSerialization.jsonObject(with: data) as? [String: Any],
            let choices = json["choices"] as? [[String: Any]],
            let first   = choices.first,
            let message = first["message"] as? [String: Any],
            let content = message["content"] as? String
        else {
            throw URLError(.cannotParseResponse)
        }

        let result = parseResult(content)
        recordRequest()
        saveCache(result, for: person)
        return result
    }

    // MARK: - Prompt Builder

    private func buildPrompt(for person: Person, template: String) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd.MM.yyyy"
        formatter.locale = Locale(identifier: "de_DE")

        let momentLines = person.sortedMoments.prefix(30).map {
            "- \(formatter.string(from: $0.createdAt)) [\($0.type.rawValue)]: \($0.text)"
        }.joined(separator: "\n")

        let logLines = person.sortedLogEntries.prefix(20).map {
            "- \(formatter.string(from: $0.loggedAt)) [\($0.type.rawValue)]: \($0.title)"
        }.joined(separator: "\n")

        let moments    = momentLines.isEmpty  ? "" : "Momente (\(person.sortedMoments.count)):\n\(momentLines)\n"
        let logEntries = logLines.isEmpty     ? "" : "Log-Einträge (\(person.sortedLogEntries.count)):\n\(logLines)\n"

        return template
            .replacingOccurrences(of: "{{personName}}",  with: person.firstName)
            .replacingOccurrences(of: "{{birthday}}",    with: birthYearContext(for: person))
            .replacingOccurrences(of: "{{interests}}",   with: person.interests.map { "Interessen: \($0)\n" } ?? "")
            .replacingOccurrences(of: "{{moments}}",     with: moments)
            .replacingOccurrences(of: "{{logEntries}}",  with: logEntries)
    }

    private func birthYearContext(for person: Person) -> String {
        guard let birthday = person.birthday else { return "" }
        let year = Calendar.current.component(.year, from: birthday)
        return "Geburtsjahr: \(year)\n"
    }

    // MARK: - Gift Cache

    private func giftCacheKey(for person: Person) -> String { "ai_gift_\(person.id.uuidString)" }

    func loadCachedGift(for person: Person) -> CachedGiftSuggestion? {
        guard let data = UserDefaults.standard.data(forKey: giftCacheKey(for: person)),
              let cached = try? JSONDecoder().decode(CachedGiftSuggestion.self, from: data)
        else { return nil }
        return cached
    }

    private func saveGiftCache(_ text: String, for person: Person) {
        let cached = CachedGiftSuggestion(text: text, generatedAt: Date())
        if let data = try? JSONEncoder().encode(cached) {
            UserDefaults.standard.set(data, forKey: giftCacheKey(for: person))
        }
    }

    // MARK: - Gift Suggestion

    func suggestGift(person: Person) async throws -> String {
        let config = AIConfig.load()

        guard let url = URL(string: config.completionsURL) else {
            throw URLError(.badURL)
        }

        let prompt = buildPrompt(for: person, template: config.giftPromptTemplate)

        let body: [String: Any] = [
            "model":   config.model,
            "stream":  false,
            "messages": [
                ["role": "system", "content": config.systemPrompt],
                ["role": "user",   "content": prompt]
            ]
        ]

        var request = URLRequest(url: url, timeoutInterval: config.timeoutSeconds)
        request.httpMethod = "POST"
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json",         forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONSerialization.data(withJSONObject: body)

        let (data, response) = try await URLSession.shared.data(for: request)

        guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }

        guard
            let json    = try JSONSerialization.jsonObject(with: data) as? [String: Any],
            let choices = json["choices"] as? [[String: Any]],
            let first   = choices.first,
            let message = first["message"] as? [String: Any],
            let content = message["content"] as? String
        else {
            throw URLError(.cannotParseResponse)
        }

        // Normalize **IDEE X:** → IDEE X:
        var normalized = content
        for i in 1...3 {
            normalized = normalized
                .replacingOccurrences(of: "**IDEE \(i):**", with: "IDEE \(i):")
                .replacingOccurrences(of: "**IDEE \(i)**:",  with: "IDEE \(i):")
        }

        recordRequest()
        saveGiftCache(normalized, for: person)
        return normalized
    }

    // MARK: - Result Parser

    private func parseResult(_ text: String) -> AIAnalysisResult {
        // Labels können als "MUSTER:" oder "**MUSTER:**" erscheinen — beides normalisieren
        var normalized = text
        for label in ["MUSTER", "BEZIEHUNG", "EMPFEHLUNG"] {
            normalized = normalized
                .replacingOccurrences(of: "**\(label):**", with: "\(label):")
                .replacingOccurrences(of: "**\(label)**:",  with: "\(label):")
                .replacingOccurrences(of: "**\(label)** :", with: "\(label):")
        }

        func extract(_ label: String) -> String {
            // Lookahead nur auf bekannte Labels, nicht auf beliebige Großbuchstaben-Sequenzen
            let pattern = "\(label):\\s*(.+?)(?=\\n(?:MUSTER|BEZIEHUNG|EMPFEHLUNG):|\\z)"
            guard
                let regex = try? NSRegularExpression(pattern: pattern, options: [.dotMatchesLineSeparators]),
                let match = regex.firstMatch(in: normalized, range: NSRange(normalized.startIndex..., in: normalized)),
                let range = Range(match.range(at: 1), in: normalized)
            else { return "" }
            return String(normalized[range]).trimmingCharacters(in: .whitespacesAndNewlines)
        }

        return AIAnalysisResult(
            patterns:       extract("MUSTER"),
            relationship:   extract("BEZIEHUNG"),
            recommendation: extract("EMPFEHLUNG")
        )
    }
}
