import Foundation
//
// 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/>.


enum SyncAuthMethod: String, CaseIterable, Codable {
    case ssh = "ssh"
    case password = "password"
    case token = "token"

    var displayName: String {
        switch self {
        case .ssh: return "SSH Key"
        case .password: return "Username + Password"
        case .token: return "Access Token"
        }
    }
}

enum SyncError: LocalizedError {
    case notConfigured
    case missingCredentials
    case gitNotFound
    case gitFailed(String)
    case repoNotCloned
    case secretsDetected([String])
    case parseError(String)

    var errorDescription: String? {
        switch self {
        case .notConfigured:
            return "Git sync not configured. Check Settings > Sync."
        case .missingCredentials:
            return "Missing credentials. Check authentication method in Settings > Sync."
        case .gitNotFound:
            return "Git not found. Install Xcode Command Line Tools."
        case .gitFailed(let message):
            return "Git command failed: \(message)"
        case .repoNotCloned:
            return "Repository not cloned. Click 'Clone Repository' first."
        case .secretsDetected(let secrets):
            return "Secrets detected in conversations: \(secrets.joined(separator: ", ")). Remove before syncing."
        case .parseError(let message):
            return "Failed to parse conversation: \(message)"
        }
    }
}

struct SyncStatus: Equatable {
    var lastSyncTime: Date?
    var uncommittedChanges: Int = 0
    var isCloned: Bool = false
    var currentBranch: String?
    var remoteStatus: String? // "up-to-date", "ahead 3", "behind 2", etc.
}

struct ConversationExport {
    let id: String
    let name: String
    let createdAt: Date
    let updatedAt: Date
    let primaryModel: String? // Primary model used in conversation
    let messages: [MessageExport]

    struct MessageExport {
        let role: String
        let content: String
        let timestamp: Date
        let tokens: Int?
        let cost: Double?
        let modelId: String? // Model that generated this message
    }

    func toMarkdown() -> String {
        var md = "# \(name)\n\n"
        md += "**ID**: `\(id)`\n"
        md += "**Created**: \(ISO8601DateFormatter().string(from: createdAt))\n"
        md += "**Updated**: \(ISO8601DateFormatter().string(from: updatedAt))\n"

        // Add primary model if available
        if let primaryModel = primaryModel {
            md += "**Primary Model**: \(primaryModel)\n"
        }

        // Calculate all unique models used
        let uniqueModels = Set(messages.compactMap { $0.modelId })
        if !uniqueModels.isEmpty {
            md += "**Models Used**: \(uniqueModels.sorted().joined(separator: ", "))\n"
        }

        md += "\n---\n\n"

        for message in messages {
            md += "## \(message.role.capitalized)\n\n"
            md += message.content + "\n\n"

            var meta: [String] = []
            if let modelId = message.modelId {
                meta.append("Model: \(modelId)")
            }
            if let tokens = message.tokens {
                meta.append("Tokens: \(tokens)")
            }
            if let cost = message.cost {
                meta.append(String(format: "Cost: $%.4f", cost))
            }
            if !meta.isEmpty {
                md += "*\(meta.joined(separator: " | "))*\n\n"
            }

            md += "---\n\n"
        }

        return md
    }

    /// Parse markdown back to ConversationExport
    static func fromMarkdown(_ markdown: String) throws -> ConversationExport {
        let lines = markdown.components(separatedBy: .newlines)
        var lineIndex = 0

        // Parse title (first line should be "# Title")
        guard lineIndex < lines.count, lines[lineIndex].hasPrefix("# ") else {
            throw SyncError.parseError("Missing title")
        }
        let name = String(lines[lineIndex].dropFirst(2)).trimmingCharacters(in: .whitespaces)
        lineIndex += 1

        // Skip empty line
        while lineIndex < lines.count && lines[lineIndex].trimmingCharacters(in: .whitespaces).isEmpty {
            lineIndex += 1
        }

        // Parse ID
        guard lineIndex < lines.count, lines[lineIndex].hasPrefix("**ID**: `") else {
            throw SyncError.parseError("Missing ID")
        }
        let idLine = lines[lineIndex]
        let idStart = idLine.index(idLine.startIndex, offsetBy: 9)
        let idEnd = idLine.lastIndex(of: "`") ?? idLine.endIndex
        let id = String(idLine[idStart..<idEnd])
        lineIndex += 1

        // Parse Created date
        guard lineIndex < lines.count, lines[lineIndex].hasPrefix("**Created**: ") else {
            throw SyncError.parseError("Missing Created date")
        }
        let createdStr = String(lines[lineIndex].dropFirst(13))
        guard let createdAt = ISO8601DateFormatter().date(from: createdStr) else {
            throw SyncError.parseError("Invalid Created date format")
        }
        lineIndex += 1

        // Parse Updated date
        guard lineIndex < lines.count, lines[lineIndex].hasPrefix("**Updated**: ") else {
            throw SyncError.parseError("Missing Updated date")
        }
        let updatedStr = String(lines[lineIndex].dropFirst(13))
        guard let updatedAt = ISO8601DateFormatter().date(from: updatedStr) else {
            throw SyncError.parseError("Invalid Updated date format")
        }
        lineIndex += 1

        // Parse optional Primary Model
        var primaryModel: String? = nil
        if lineIndex < lines.count && lines[lineIndex].hasPrefix("**Primary Model**: ") {
            primaryModel = String(lines[lineIndex].dropFirst(19)).trimmingCharacters(in: .whitespaces)
            lineIndex += 1
        }

        // Skip optional Models Used line
        if lineIndex < lines.count && lines[lineIndex].hasPrefix("**Models Used**: ") {
            lineIndex += 1
        }

        // Skip to first message (past the ---)
        while lineIndex < lines.count && !lines[lineIndex].hasPrefix("## ") {
            lineIndex += 1
        }

        // Parse messages
        var messages: [MessageExport] = []
        while lineIndex < lines.count {
            // Parse role (## User or ## Assistant)
            guard lines[lineIndex].hasPrefix("## ") else {
                lineIndex += 1
                continue
            }
            let role = String(lines[lineIndex].dropFirst(3)).lowercased().trimmingCharacters(in: .whitespaces)
            lineIndex += 1

            // Skip empty line
            while lineIndex < lines.count && lines[lineIndex].trimmingCharacters(in: .whitespaces).isEmpty {
                lineIndex += 1
            }

            // Parse content (until metadata line or ---)
            var content = ""
            while lineIndex < lines.count &&
                  !lines[lineIndex].hasPrefix("*Tokens:") &&
                  !lines[lineIndex].hasPrefix("---") &&
                  !lines[lineIndex].hasPrefix("## ") {
                if !content.isEmpty {
                    content += "\n"
                }
                content += lines[lineIndex]
                lineIndex += 1
            }
            content = content.trimmingCharacters(in: .whitespacesAndNewlines)

            // Parse metadata if present
            var modelId: String? = nil
            var tokens: Int? = nil
            var cost: Double? = nil
            if lineIndex < lines.count && lines[lineIndex].hasPrefix("*") {
                let metaLine = lines[lineIndex]
                // Extract modelId: "Model: gpt-4"
                if let modelMatch = metaLine.range(of: "Model: ([^|\\*]+)", options: .regularExpression) {
                    let modelStr = String(metaLine[modelMatch]).dropFirst(7)
                    modelId = modelStr.trimmingCharacters(in: .whitespaces)
                }
                // Extract tokens: "Tokens: 50"
                if let tokensMatch = metaLine.range(of: "Tokens: (\\d+)", options: .regularExpression) {
                    let tokensStr = String(metaLine[tokensMatch]).dropFirst(8)
                    tokens = Int(tokensStr)
                }
                // Extract cost: "Cost: $0.0001"
                if let costMatch = metaLine.range(of: "Cost: \\$([0-9.]+)", options: .regularExpression) {
                    let costStr = String(metaLine[costMatch]).dropFirst(7)
                    cost = Double(costStr)
                }
                lineIndex += 1
            }

            // Create message
            messages.append(MessageExport(
                role: role,
                content: content,
                timestamp: Date(), // Use current time as we don't store timestamps in markdown yet
                tokens: tokens,
                cost: cost,
                modelId: modelId
            ))

            // Skip to next message or end
            while lineIndex < lines.count && !lines[lineIndex].hasPrefix("## ") {
                lineIndex += 1
            }
        }

        return ConversationExport(
            id: id,
            name: name,
            createdAt: createdAt,
            updatedAt: updatedAt,
            primaryModel: primaryModel,
            messages: messages
        )
    }
}
