import Foundation
import CoreData

enum BackupLocation: String {
    case local = "On device"
    case iCloud = "iCloud"
}

struct BackupRecord: Identifiable {
    let id: String
    let url: URL
    let date: Date
    let size: Int64
    let location: BackupLocation
}

class BackupService {
    static let shared = BackupService()

    private let fileManager: FileManager
    private let dateProvider: () -> Date
    private let localBaseDirectoryProvider: () -> URL?
    private let iCloudBaseDirectoryProvider: () -> URL?
    private let exportProvider: () -> String
    private let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMdd-HHmmss"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()

    private init() {
        fileManager = .default
        dateProvider = Date.init
        localBaseDirectoryProvider = { [fileManager] in
            fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
        }
        iCloudBaseDirectoryProvider = { [fileManager] in
            fileManager.url(forUbiquityContainerIdentifier: nil)
        }
        exportProvider = {
            let context = CoreDataStack.shared.viewContext
            let sources = BackupService.fetchAllSources(in: context)
            let categories = BackupService.fetchAllCategories(in: context)
            return ExportService.shared.exportToJSON(sources: sources, categories: categories)
        }
    }

    init(
        fileManager: FileManager = .default,
        dateProvider: @escaping () -> Date,
        localBaseDirectoryProvider: @escaping () -> URL?,
        iCloudBaseDirectoryProvider: @escaping () -> URL?,
        exportProvider: @escaping () -> String
    ) {
        self.fileManager = fileManager
        self.dateProvider = dateProvider
        self.localBaseDirectoryProvider = localBaseDirectoryProvider
        self.iCloudBaseDirectoryProvider = iCloudBaseDirectoryProvider
        self.exportProvider = exportProvider
    }

    func createBackup(retentionCount: Int, includeICloud: Bool) -> [BackupRecord] {
        let timestamp = dateFormatter.string(from: dateProvider())
        let fileName = "backup-\(timestamp).json"

        let content = exportProvider()

        var records: [BackupRecord] = []

        if let localDir = backupDirectory() {
            let localURL = localDir.appendingPathComponent(fileName)
            write(content: content, to: localURL)
            pruneBackups(in: localDir, keep: retentionCount, location: .local)
            records.append(contentsOf: listBackups(in: localDir, location: .local))
        }

        if includeICloud, let iCloudDir = iCloudBackupDirectory() {
            let iCloudURL = iCloudDir.appendingPathComponent(fileName)
            write(content: content, to: iCloudURL)
            pruneBackups(in: iCloudDir, keep: retentionCount, location: .iCloud)
            records.append(contentsOf: listBackups(in: iCloudDir, location: .iCloud))
        }

        return records.sorted { $0.date > $1.date }
    }

    func listAllBackups(includeICloud: Bool) -> [BackupRecord] {
        var records: [BackupRecord] = []
        if let localDir = backupDirectory() {
            records.append(contentsOf: listBackups(in: localDir, location: .local))
        }
        if includeICloud, let iCloudDir = iCloudBackupDirectory() {
            records.append(contentsOf: listBackups(in: iCloudDir, location: .iCloud))
        }
        return records.sorted { $0.date > $1.date }
    }

    private func backupDirectory() -> URL? {
        guard let base = localBaseDirectoryProvider() else {
            return nil
        }
        let dir = base.appendingPathComponent("Backups", isDirectory: true)
        ensureDirectoryExists(dir)
        return dir
    }

    private func iCloudBackupDirectory() -> URL? {
        guard let base = iCloudBaseDirectoryProvider() else { return nil }
        let dir = base.appendingPathComponent("Documents/Backups", isDirectory: true)
        ensureDirectoryExists(dir)
        return dir
    }

    private func ensureDirectoryExists(_ url: URL) {
        if !fileManager.fileExists(atPath: url.path) {
            try? fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
        }
    }

    private func write(content: String, to url: URL) {
        do {
            try content.write(to: url, atomically: true, encoding: .utf8)
        } catch {
            print("Backup write failed: \(error)")
        }
    }

    private func listBackups(in directory: URL, location: BackupLocation) -> [BackupRecord] {
        guard let files = try? fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: [.contentModificationDateKey, .fileSizeKey]) else {
            return []
        }

        return files.compactMap { url in
            guard url.lastPathComponent.hasPrefix("backup-"),
                  url.pathExtension.lowercased() == "json" else {
                return nil
            }

            let name = url.deletingPathExtension().lastPathComponent
            let date = parseDate(from: name) ?? (try? url.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? Date()
            let size = (try? url.resourceValues(forKeys: [.fileSizeKey]).fileSize).map { Int64($0) } ?? 0

            return BackupRecord(
                id: "\(location.rawValue)-\(name)",
                url: url,
                date: date,
                size: size,
                location: location
            )
        }
    }

    private func pruneBackups(in directory: URL, keep: Int, location: BackupLocation) {
        guard keep > 0 else { return }
        let backups = listBackups(in: directory, location: location).sorted { $0.date > $1.date }
        let toDelete = backups.dropFirst(keep)
        for backup in toDelete {
            try? fileManager.removeItem(at: backup.url)
        }
    }

    private func parseDate(from filename: String) -> Date? {
        let parts = filename.split(separator: "-")
        guard parts.count >= 3 else { return nil }
        let dateString = "\(parts[1])-\(parts[2])"
        return dateFormatter.date(from: dateString)
    }

    private static func fetchAllSources(in context: NSManagedObjectContext) -> [InvestmentSource] {
        let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(keyPath: \InvestmentSource.name, ascending: true)]
        return (try? context.fetch(request)) ?? []
    }

    private static func fetchAllCategories(in context: NSManagedObjectContext) -> [Category] {
        let request: NSFetchRequest<Category> = Category.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Category.name, ascending: true)]
        return (try? context.fetch(request)) ?? []
    }
}
