//
//  EmailHandlerService.swift
//  oAI
//
//  AI-powered email auto-responder service
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// oAI is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
// Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with oAI. If not, see <https://www.gnu.org/licenses/>.


import Foundation
import os

@Observable
final class EmailHandlerService {
    static let shared = EmailHandlerService()

    private let settings = SettingsService.shared
    private let emailService = EmailService.shared
    private let emailLog = EmailLogService.shared
    private let mcp = MCPService.shared
    private let log = Logger(subsystem: "com.oai.oAI", category: "email-handler")

    // Rate limiting
    private var emailsProcessedThisHour: Int = 0
    private var hourResetTimer: Timer?

    // Processing state
    private var isProcessing = false

    private init() {}

    // MARK: - Lifecycle

    /// Start email handler (call at app startup)
    func start() {
        guard settings.emailHandlerEnabled else {
            log.info("Email handler disabled")
            return
        }

        guard settings.emailHandlerConfigured else {
            log.warning("Email handler not configured properly")
            return
        }

        log.info("Starting email handler...")

        // Set up email monitoring callback
        emailService.onNewEmail = { [weak self] email in
            Task {
                await self?.handleIncomingEmail(email)
            }
        }

        // Start IMAP IDLE monitoring
        emailService.startMonitoring()

        // Start rate limit timer
        startRateLimitTimer()

        log.info("Email handler started successfully")
    }

    /// Stop email handler
    func stop() {
        log.info("Stopping email handler...")
        emailService.stopMonitoring()
        hourResetTimer?.invalidate()
        hourResetTimer = nil
        log.info("Email handler stopped")
    }

    // MARK: - Email Processing

    private func handleIncomingEmail(_ email: IncomingEmail) async {
        log.info("New email received from \(email.from): \(email.subject)")

        // Check rate limiting
        if self.settings.emailRateLimitEnabled && self.settings.emailRateLimitPerHour < 100 {
            if self.emailsProcessedThisHour >= self.settings.emailRateLimitPerHour {
                log.warning("Rate limit exceeded (\(self.emailsProcessedThisHour)/\(self.settings.emailRateLimitPerHour)), skipping email")
                emailLog.logError(
                    sender: email.from,
                    subject: email.subject,
                    emailContent: String(email.body.prefix(200)),
                    errorMessage: "Rate limit exceeded (\(self.settings.emailRateLimitPerHour) emails/hour)",
                    modelId: nil
                )
                return
            }
        }

        // Prevent concurrent processing
        guard !isProcessing else {
            log.warning("Already processing an email, queueing not implemented")
            return
        }

        isProcessing = true
        defer { isProcessing = false }

        // Process with retry
        var attemptCount = 0
        let maxAttempts = 2

        while attemptCount < maxAttempts {
            attemptCount += 1

            do {
                try await processEmailWithAI(email, attempt: attemptCount)
                emailsProcessedThisHour += 1

                // Delete email after successful processing
                try? await deleteEmail(email.uid)

                log.info("Successfully processed and deleted email (attempt \(attemptCount))")
                return
            } catch {
                log.error("Failed to process email (attempt \(attemptCount)): \(error.localizedDescription)")

                if attemptCount >= maxAttempts {
                    // Send error email to sender
                    try? await sendErrorEmail(to: email.from, subject: email.subject, error: error)

                    // Log error
                    emailLog.logError(
                        sender: email.from,
                        subject: email.subject,
                        emailContent: String(email.body.prefix(200)),
                        errorMessage: error.localizedDescription,
                        modelId: settings.emailHandlerModel
                    )
                }
            }
        }
    }

    private func processEmailWithAI(_ email: IncomingEmail, attempt: Int) async throws {
        let startTime = Date()

        log.info("Processing email with AI (attempt \(attempt))...")

        // Get AI provider for email handling
        guard let provider = getEmailProvider() else {
            throw EmailServiceError.notConfigured
        }

        // Build AI prompt
        let prompt = buildEmailPrompt(from: email)

        // Prepare tools (read-only MCP access)
        var tools: [Tool]? = nil
        if settings.mcpEnabled && !mcp.allowedFolders.isEmpty {
            tools = mcp.getToolSchemas().filter { tool in
                isReadOnlyTool(tool.function.name)
            }
        }

        // Build messages
        let userMessage = Message(
            role: .user,
            content: prompt
        )

        // Create chat request
        // IMPORTANT: Email handler uses ONLY its own system prompt (custom or default)
        // All other prompts (main chat system prompt, user custom prompts) are excluded
        // This ensures complete isolation of email handling behavior
        let request = ChatRequest(
            messages: [userMessage],
            model: settings.emailHandlerModel,
            stream: false,
            maxTokens: settings.emailMaxTokens,
            temperature: 0.7,
            systemPrompt: getEmailSystemPrompt(), // Email-specific prompt ONLY
            tools: tools,
            onlineMode: settings.emailOnlineMode,  // User-configurable online mode for emails
            imageGeneration: false  // Disable image generation for emails
        )

        // Call AI (non-streaming)
        let response = try await provider.chat(request: request)

        let fullResponse = response.content
        let promptTokens = response.usage?.promptTokens
        let completionTokens = response.usage?.completionTokens
        let totalTokens = response.usage.map { $0.promptTokens + $0.completionTokens }

        // Calculate cost if we have pricing info
        var totalCost: Double? = nil
        if let usage = response.usage,
           let models = try? await provider.listModels(),
           let modelInfo = models.first(where: { $0.id == settings.emailHandlerModel }) {
            totalCost = (Double(usage.promptTokens) * modelInfo.pricing.prompt / 1_000_000) +
                       (Double(usage.completionTokens) * modelInfo.pricing.completion / 1_000_000)
        }

        let responseTime = Date().timeIntervalSince(startTime)

        log.info("AI response generated in \(String(format: "%.2f", responseTime))s")

        // Generate HTML email with stats
        let htmlBody = generateHTMLEmail(
            aiResponse: fullResponse,
            originalEmail: email,
            responseTime: responseTime,
            promptTokens: promptTokens,
            completionTokens: completionTokens,
            cost: totalCost
        )

        // Send response email
        let replySubject = email.subject.hasPrefix("Re:") ? email.subject : "Re: \(email.subject)"

        let messageId = try await emailService.sendEmail(
            to: email.from,
            subject: replySubject,
            body: fullResponse, // Plain text fallback
            htmlBody: htmlBody,
            inReplyTo: email.messageId
        )

        log.info("Response email sent: \(messageId)")

        // Log success
        emailLog.logSuccess(
            sender: email.from,
            subject: email.subject,
            emailContent: String(email.body.prefix(500)),
            aiResponse: String(fullResponse.prefix(500)),
            tokens: totalTokens,
            cost: totalCost,
            responseTime: responseTime,
            modelId: settings.emailHandlerModel
        )
    }

    // MARK: - Prompt Building

    private func buildEmailPrompt(from email: IncomingEmail) -> String {
        // Remove subject identifier from subject
        var cleanSubject = email.subject
        if let range = cleanSubject.range(of: settings.emailSubjectIdentifier) {
            cleanSubject.removeSubrange(range)
            cleanSubject = cleanSubject.trimmingCharacters(in: .whitespacesAndNewlines)
        }

        let prompt = """
        You have received an email that requires a response. Please provide a helpful, professional reply.

        From: \(email.from)
        Subject: \(cleanSubject)
        Date: \(email.receivedDate.formatted())

        Email Content:
        \(email.body)

        ---

        Please provide a complete, well-formatted response to this email. Write the reply directly — do not wrap it in code fences or markdown blocks.
        """

        return prompt
    }

    /// Get the email handler system prompt
    /// - Returns: Email-specific system prompt (custom if set, default otherwise)
    /// - Note: This is completely isolated from the main chat system prompt.
    ///         No other prompts are merged or concatenated.
    private func getEmailSystemPrompt() -> String {
        // ISOLATION: Use custom email prompt if provided, otherwise use default email prompt
        // This completely overrides and replaces any other system prompts
        // Main chat prompts and user custom prompts are NOT used for email handling
        if let customPrompt = settings.emailHandlerSystemPrompt, !customPrompt.isEmpty {
            return customPrompt
        }

        // Default email system prompt (used only when no custom email prompt is set)
        return """
        You are an AI email assistant. You respond to emails on behalf of the user.

        Guidelines:
        - Be professional and courteous
        - Keep responses concise and relevant
        - Use proper email etiquette
        - Format your response using Markdown (bold, lists, headings are welcome)
        - Do NOT wrap your response in code fences or ```html blocks — write the email body directly
        - If you need information from files, you have read-only access via MCP tools
        - Never claim to write, modify, or delete files (read-only access)
        - Sign emails appropriately

        Your response will be automatically formatted and sent via email.
        """
    }

    // MARK: - HTML Email Generation

    private func generateHTMLEmail(
        aiResponse: String,
        originalEmail: IncomingEmail,
        responseTime: TimeInterval,
        promptTokens: Int?,
        completionTokens: Int?,
        cost: Double?
    ) -> String {
        // Convert markdown to HTML (basic implementation)
        let htmlContent = markdownToHTML(aiResponse)

        // Format stats
        let timeFormatted = String(format: "%.2f", responseTime)
        let totalTokens = (promptTokens ?? 0) + (completionTokens ?? 0)
        let costFormatted = cost.map { String(format: "$%.4f", $0) } ?? "N/A"

        return """
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <style>
                body {
                    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                    line-height: 1.6;
                    color: #333;
                    max-width: 600px;
                    margin: 0 auto;
                    padding: 20px;
                }
                .content {
                    background: #ffffff;
                    padding: 20px;
                    border-radius: 8px;
                }
                .stats {
                    margin-top: 30px;
                    padding: 15px;
                    background: #f8f9fa;
                    border-radius: 6px;
                    font-size: 11px;
                    color: #666;
                }
                .stats-grid {
                    display: grid;
                    grid-template-columns: auto 1fr;
                    gap: 8px 12px;
                    margin-top: 8px;
                }
                .stats-label {
                    font-weight: 600;
                    color: #555;
                }
                .stats-value {
                    color: #666;
                }
                .footer {
                    margin-top: 20px;
                    padding-top: 15px;
                    border-top: 1px solid #e0e0e0;
                    font-size: 12px;
                    color: #666;
                    text-align: center;
                }
                code {
                    background: #f5f5f5;
                    padding: 2px 6px;
                    border-radius: 3px;
                    font-family: monospace;
                }
                pre {
                    background: #f5f5f5;
                    padding: 12px;
                    border-radius: 6px;
                    overflow-x: auto;
                }
            </style>
        </head>
        <body>
            <div class="content">
                \(htmlContent)
            </div>
            <div class="stats">
                <div style="font-weight: 600; margin-bottom: 6px; color: #555;">📊 Processing Stats</div>
                <div class="stats-grid">
                    <span class="stats-label">Response Time:</span>
                    <span class="stats-value">\(timeFormatted)s</span>
                    <span class="stats-label">Tokens Used:</span>
                    <span class="stats-value">\(totalTokens.formatted()) (\(promptTokens ?? 0) prompt + \(completionTokens ?? 0) completion)</span>
                    <span class="stats-label">Cost:</span>
                    <span class="stats-value">\(costFormatted)</span>
                </div>
            </div>
            <div class="footer">
                <p>🤖 This response was generated by AI using oAI Email Handler</p>
            </div>
        </body>
        </html>
        """
    }

    private func markdownToHTML(_ markdown: String) -> String {
        var text = markdown.trimmingCharacters(in: .whitespacesAndNewlines)

        // Strip outer code fence wrapping the entire response
        // Models sometimes wrap their reply in ```html ... ``` or ``` ... ```
        let lines = text.components(separatedBy: "\n")
        if lines.count >= 2 {
            let first = lines[0].trimmingCharacters(in: .whitespaces)
            let last = lines[lines.count - 1].trimmingCharacters(in: .whitespaces)
            if (first == "```" || first.hasPrefix("```")) && last == "```" {
                text = lines.dropFirst().dropLast().joined(separator: "\n")
                    .trimmingCharacters(in: .whitespacesAndNewlines)
            }
        }

        // Strip any remaining fenced code blocks (preserve content, remove fences)
        text = text.replacingOccurrences(
            of: #"```[a-z]*\n([\s\S]*?)```"#,
            with: "$1",
            options: .regularExpression
        )

        // Bold
        text = text.replacingOccurrences(of: #"\*\*(.+?)\*\*"#, with: "<strong>$1</strong>", options: .regularExpression)

        // Italic (avoid matching bold's leftover *)
        text = text.replacingOccurrences(of: #"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)"#, with: "<em>$1</em>", options: .regularExpression)

        // Inline code
        text = text.replacingOccurrences(of: #"`([^`\n]+)`"#, with: "<code>$1</code>", options: .regularExpression)

        // Split into paragraphs on double newlines, wrap each in <p>
        let paragraphs = text.components(separatedBy: "\n\n")
            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
            .filter { !$0.isEmpty }
            .map { para in
                let withBreaks = para.replacingOccurrences(of: "\n", with: "<br>")
                return "<p>\(withBreaks)</p>"
            }

        return paragraphs.joined(separator: "\n")
    }

    // MARK: - Error Handling

    private func sendErrorEmail(to: String, subject: String, error: Error) async throws {
        let errorHTML = """
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <style>
                body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
                .error { background: #fee; padding: 20px; border-left: 4px solid #f00; }
            </style>
        </head>
        <body>
            <div class="error">
                <h2>⚠️ Email Processing Error</h2>
                <p>We encountered an error while processing your email:</p>
                <p><strong>\(error.localizedDescription)</strong></p>
                <p>Please try again later or contact support if the problem persists.</p>
            </div>
        </body>
        </html>
        """

        _ = try await emailService.sendEmail(
            to: to,
            subject: "Re: \(subject) - Processing Error",
            body: "An error occurred: \(error.localizedDescription)",
            htmlBody: errorHTML
        )
    }

    // MARK: - Helpers

    private func deleteEmail(_ uid: UInt32) async throws {
        guard let host = settings.emailImapHost,
              let username = settings.emailUsername,
              let password = settings.emailPassword else {
            return
        }

        let client = IMAPClient(
            host: host,
            port: UInt16(settings.emailImapPort),
            username: username,
            password: password,
            useTLS: true
        )

        try await client.connect()
        try await client.login()
        try await client.selectMailbox("INBOX")
        try await client.deleteEmail(uid: uid)
        try await client.expunge()
        client.disconnect()

        log.info("Deleted email UID \(uid)")
    }

    private func getEmailProvider() -> AIProvider? {
        let providerNameString = settings.emailHandlerProvider
        let registry = ProviderRegistry.shared

        // Convert string to Settings.Provider enum
        guard let providerType = Settings.Provider(rawValue: providerNameString) else {
            log.error("Invalid provider name: '\(providerNameString)'")
            return nil
        }

        // Check if provider is configured
        let configuredProviders = registry.configuredProviders
        guard configuredProviders.contains(providerType) else {
            log.error("Email handler provider '\(providerNameString)' not configured (no API key)")
            return nil
        }

        // Get provider instance
        return registry.getProvider(for: providerType)
    }

    private func isReadOnlyTool(_ toolName: String) -> Bool {
        // Only allow read-only MCP tools for email handling
        let readOnlyTools = ["read_file", "list_directory", "search_files", "get_file_info"]
        return readOnlyTools.contains(toolName)
    }

    private func startRateLimitTimer() {
        // Reset counter every hour
        hourResetTimer = Timer.scheduledTimer(withTimeInterval: 3600, repeats: true) { [weak self] _ in
            self?.emailsProcessedThisHour = 0
            self?.log.info("Rate limit counter reset")
        }
    }
}
