import Foundation
import CoreData
import Combine

class ImportService {
    static let shared = ImportService()

    private init() {}

    struct ImportResult {
        let accountsCreated: Int
        let sourcesCreated: Int
        let snapshotsCreated: Int
        let snapshotsUpdated: Int
        let errors: [String]
    }

    struct ImportProgress {
        let completed: Int
        let total: Int
        let message: String

        var fraction: Double {
            guard total > 0 else { return 0 }
            return min(max(Double(completed) / Double(total), 0), 1)
        }
    }

    enum ImportFormat {
        case csv
        case json
    }

    struct ImportedAccount {
        let name: String
        let currency: String?
        let inputMode: InputMode
        let notificationFrequency: NotificationFrequency
        let customFrequencyMonths: Int
        let categories: [ImportedCategory]
    }

    struct ImportedCategory {
        let name: String
        let colorHex: String?
        let icon: String?
        let sources: [ImportedSource]
    }

    struct ImportedSource {
        let name: String
        let snapshots: [ImportedSnapshot]
    }

    struct ImportedSnapshot {
        let date: Date
        let value: Decimal
        let contribution: Decimal?
        let notes: String?
    }

    func importData(
        content: String,
        format: ImportFormat,
        allowMultipleAccounts: Bool,
        defaultAccountName: String? = nil
    ) -> ImportResult {
        switch format {
        case .csv:
            let parsed = parseCSV(
                content,
                allowMultipleAccounts: allowMultipleAccounts,
                defaultAccountName: defaultAccountName
            )
            return applyImport(parsed, context: CoreDataStack.shared.viewContext)
        case .json:
            let parsed = parseJSON(
                content,
                allowMultipleAccounts: allowMultipleAccounts,
                defaultAccountName: defaultAccountName
            )
            return applyImport(parsed, context: CoreDataStack.shared.viewContext)
        }
    }

    func importDataAsync(
        content: String,
        format: ImportFormat,
        allowMultipleAccounts: Bool,
        defaultAccountName: String? = nil,
        progress: @escaping (ImportProgress) -> Void
    ) async -> ImportResult {
        await withCheckedContinuation { continuation in
            CoreDataStack.shared.performBackgroundTask { context in
                let parsed: [ImportedAccount]
                switch format {
                case .csv:
                    parsed = self.parseCSV(
                        content,
                        allowMultipleAccounts: allowMultipleAccounts,
                        defaultAccountName: defaultAccountName
                    )
                case .json:
                    parsed = self.parseJSON(
                        content,
                        allowMultipleAccounts: allowMultipleAccounts,
                        defaultAccountName: defaultAccountName
                    )
                }

                let totalSnapshots = parsed.reduce(0) { total, account in
                    total + account.categories.reduce(0) { subtotal, category in
                        subtotal + category.sources.reduce(0) { sourceTotal, source in
                            sourceTotal + source.snapshots.count
                        }
                    }
                }

                DispatchQueue.main.async {
                    progress(ImportProgress(completed: 0, total: totalSnapshots, message: "Importing data"))
                }

                let result = self.applyImport(parsed, context: context) { completed in
                    DispatchQueue.main.async {
                        progress(ImportProgress(
                            completed: completed,
                            total: totalSnapshots,
                            message: "Imported \(completed) of \(totalSnapshots) snapshots"
                        ))
                    }
                }

                continuation.resume(returning: result)
            }
        }
    }

    static func sampleCSV() -> String {
        return """
Account,Category,Source,Date,Value,Contribution,Notes
Personal,Stocks,Index Fund,2024-01-01,15000,12000,Long-term
Personal,Crypto,BTC,2024-01-01,3200,,Cold storage
Personal,Real Estate,Rental Property,2024-01-01,82000,80000,Estimated value
"""
    }

    static func sampleJSON() -> String {
        return """
{
  "version": 2,
  "currency": "EUR",
  "accounts": [{
    "name": "Personal",
    "inputMode": "simple",
    "notificationFrequency": "monthly",
    "categories": [{
      "name": "Stocks",
      "color": "#3B82F6",
      "icon": "chart.line.uptrend.xyaxis",
      "sources": [{
        "name": "Index Fund",
        "snapshots": [{
          "date": "2024-01-01T00:00:00Z",
          "value": 15000,
          "contribution": 12000
        }]
      }]
    }]
  }]
}
"""
    }

    // MARK: - Parsing

    private func parseCSV(
        _ content: String,
        allowMultipleAccounts: Bool,
        defaultAccountName: String?
    ) -> [ImportedAccount] {
        let rows = parseCSVRows(content)
        guard rows.count > 1 else { return [] }

        let headers = rows[0].map { $0.lowercased().trimmingCharacters(in: .whitespaces) }
        let indexOfAccount = headers.firstIndex(of: "account")
        let indexOfCategory = headers.firstIndex(of: "category")
        let indexOfSource = headers.firstIndex(of: "source")
        let indexOfDate = headers.firstIndex(of: "date")
        let indexOfValue = headers.firstIndex(where: { $0.hasPrefix("value") })
        let indexOfContribution = headers.firstIndex(where: { $0.hasPrefix("contribution") })
        let indexOfNotes = headers.firstIndex(of: "notes")

        var grouped: [String: [String: [String: [ImportedSnapshot]]]] = [:]

        for row in rows.dropFirst() {
            let providedAccount = indexOfAccount.flatMap { row.safeValue(at: $0) }
            let fallbackAccount = defaultAccountName ?? "Personal"
            let normalizedAccount = (providedAccount ?? "")
                .trimmingCharacters(in: .whitespacesAndNewlines)
            let rawAccountName = normalizedAccount.isEmpty ? fallbackAccount : normalizedAccount
            let accountName = allowMultipleAccounts ? rawAccountName : fallbackAccount

            guard let categoryName = indexOfCategory.flatMap({ row.safeValue(at: $0) }), !categoryName.isEmpty,
                  let sourceName = indexOfSource.flatMap({ row.safeValue(at: $0) }), !sourceName.isEmpty,
                  let dateString = indexOfDate.flatMap({ row.safeValue(at: $0) }),
                  let valueString = indexOfValue.flatMap({ row.safeValue(at: $0) }) else {
                continue
            }

            guard let date = parseDate(dateString),
                  let value = parseDecimal(valueString) else { continue }

            let contribution = indexOfContribution
                .flatMap { row.safeValue(at: $0) }
                .flatMap(parseDecimal)
            let notes = indexOfNotes
                .flatMap { row.safeValue(at: $0) }
                .flatMap { $0.isEmpty ? nil : $0 }

            let snapshot = ImportedSnapshot(
                date: date,
                value: value,
                contribution: contribution,
                notes: notes
            )

            grouped[accountName, default: [:]][categoryName, default: [:]][sourceName, default: []].append(snapshot)
        }

        return grouped.map { accountName, categories in
            let importedCategories = categories.map { categoryName, sources in
                let importedSources = sources.map { sourceName, snapshots in
                    ImportedSource(name: sourceName, snapshots: snapshots)
                }
                return ImportedCategory(name: categoryName, colorHex: nil, icon: nil, sources: importedSources)
            }

            return ImportedAccount(
                name: accountName,
                currency: nil,
                inputMode: .simple,
                notificationFrequency: .monthly,
                customFrequencyMonths: 1,
                categories: importedCategories
            )
        }
    }

    private func parseJSON(
        _ content: String,
        allowMultipleAccounts: Bool,
        defaultAccountName: String?
    ) -> [ImportedAccount] {
        guard let data = content.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            return []
        }

        if let accountsArray = json["accounts"] as? [[String: Any]] {
            return accountsArray.compactMap { accountDict in
                let rawName = accountDict["name"] as? String ?? "Personal"
                let fallbackName = defaultAccountName ?? "Personal"
                let name = allowMultipleAccounts ? rawName : fallbackName

                let currency = accountDict["currency"] as? String
                let inputMode = InputMode(rawValue: accountDict["inputMode"] as? String ?? "") ?? .simple
                let notificationFrequency = NotificationFrequency(
                    rawValue: accountDict["notificationFrequency"] as? String ?? ""
                ) ?? .monthly
                let customFrequencyMonths = accountDict["customFrequencyMonths"] as? Int ?? 1

                let categoriesArray = accountDict["categories"] as? [[String: Any]] ?? []
                let categories = categoriesArray.map { categoryDict in
                    let categoryName = categoryDict["name"] as? String ?? "Uncategorized"
                    let colorHex = categoryDict["color"] as? String
                    let icon = categoryDict["icon"] as? String
                    let sourcesArray = categoryDict["sources"] as? [[String: Any]] ?? []
                    let sources = sourcesArray.map { sourceDict in
                        let sourceName = sourceDict["name"] as? String ?? "Source"
                        let snapshotsArray = sourceDict["snapshots"] as? [[String: Any]] ?? []
                        let snapshots = snapshotsArray.compactMap { snapshotDict -> ImportedSnapshot? in
                            guard let dateString = snapshotDict["date"] as? String,
                                  let date = ISO8601DateFormatter().date(from: dateString),
                                  let value = snapshotDict["value"] as? Double else {
                                return nil
                            }

                            let contribution = (snapshotDict["contribution"] as? Double).map { Decimal($0) }
                            let notes = snapshotDict["notes"] as? String
                            return ImportedSnapshot(
                                date: date,
                                value: Decimal(value),
                                contribution: contribution,
                                notes: notes
                            )
                        }

                        return ImportedSource(name: sourceName, snapshots: snapshots)
                    }

                    return ImportedCategory(
                        name: categoryName,
                        colorHex: colorHex,
                        icon: icon,
                        sources: sources
                    )
                }

                return ImportedAccount(
                    name: name,
                    currency: currency,
                    inputMode: inputMode,
                    notificationFrequency: notificationFrequency,
                    customFrequencyMonths: customFrequencyMonths,
                    categories: categories
                )
            }
        }

        // Legacy JSON: categories only
        if let categoriesArray = json["categories"] as? [[String: Any]] {
            let categories = categoriesArray.map { categoryDict in
                let categoryName = categoryDict["name"] as? String ?? "Uncategorized"
                let colorHex = categoryDict["color"] as? String
                let icon = categoryDict["icon"] as? String
                let sourcesArray = categoryDict["sources"] as? [[String: Any]] ?? []
                let sources = sourcesArray.map { sourceDict in
                    let sourceName = sourceDict["name"] as? String ?? "Source"
                    let snapshotsArray = sourceDict["snapshots"] as? [[String: Any]] ?? []
                    let snapshots = snapshotsArray.compactMap { snapshotDict -> ImportedSnapshot? in
                        guard let dateString = snapshotDict["date"] as? String,
                              let date = ISO8601DateFormatter().date(from: dateString),
                              let value = snapshotDict["value"] as? Double else {
                            return nil
                        }

                        let contribution = (snapshotDict["contribution"] as? Double).map { Decimal($0) }
                        let notes = snapshotDict["notes"] as? String
                        return ImportedSnapshot(
                            date: date,
                            value: Decimal(value),
                            contribution: contribution,
                            notes: notes
                        )
                    }

                    return ImportedSource(name: sourceName, snapshots: snapshots)
                }

                return ImportedCategory(
                    name: categoryName,
                    colorHex: colorHex,
                    icon: icon,
                    sources: sources
                )
            }

            let fallbackName = allowMultipleAccounts ? "Personal" : (defaultAccountName ?? "Personal")
            return [
                ImportedAccount(
                    name: fallbackName,
                    currency: json["currency"] as? String,
                    inputMode: .simple,
                    notificationFrequency: .monthly,
                    customFrequencyMonths: 1,
                    categories: categories
                )
            ]
        }

        return []
    }

    // MARK: - Apply Import

    private func applyImport(
        _ accounts: [ImportedAccount],
        context: NSManagedObjectContext,
        snapshotProgress: ((Int) -> Void)? = nil
    ) -> ImportResult {
        let accountRepository = AccountRepository(context: context)
        let categoryRepository = CategoryRepository(context: context)
        let sourceRepository = InvestmentSourceRepository(context: context)
        let snapshotRepository = SnapshotRepository(context: context)

        var accountsCreated = 0
        var sourcesCreated = 0
        var snapshotsCreated = 0
        var snapshotsUpdated = 0
        var errors: [String] = []

        let existingCategories = fetchCategories(in: context)
        var categoryLookup = buildCategoryLookup(from: existingCategories)
        let otherCategory = resolveExistingCategory(named: "Other", lookup: categoryLookup)
            ?? categoryRepository.createCategory(
                name: "Other",
                colorHex: "#64748B",
                icon: "ellipsis.circle.fill"
            )
        categoryLookup[normalizedCategoryName(otherCategory.name)] = otherCategory

        var completionDatesByMonth: [String: Date] = [:]

        // Build lookup of existing accounts by fetching directly from database
        let existingAccountsLookup = fetchAccountsLookup(in: context)

        for importedAccount in accounts {
            let existingAccount = existingAccountsLookup[importedAccount.name]
            let account: Account
            if let existing = existingAccount {
                account = existing
            } else {
                account = accountRepository.createAccount(
                    name: importedAccount.name,
                    currency: importedAccount.currency,
                    inputMode: importedAccount.inputMode,
                    notificationFrequency: importedAccount.notificationFrequency,
                    customFrequencyMonths: importedAccount.customFrequencyMonths
                )
                accountsCreated += 1
            }

            for importedCategory in importedAccount.categories {
                let existingCategory = resolveExistingCategory(
                    named: importedCategory.name,
                    lookup: categoryLookup
                )
                let shouldUseOther = existingCategory == nil &&
                    importedCategory.colorHex == nil &&
                    importedCategory.icon == nil
                let resolvedName = canonicalCategoryName(for: importedCategory.name) ?? importedCategory.name
                let category = existingCategory ?? (shouldUseOther
                    ? otherCategory
                    : categoryRepository.createCategory(
                        name: resolvedName,
                        colorHex: importedCategory.colorHex ?? "#3B82F6",
                        icon: importedCategory.icon ?? "chart.pie.fill"
                    ))
                categoryLookup[normalizedCategoryName(category.name)] = category

                for importedSource in importedCategory.sources {
                    let accountId = account.safeId
                    let existingSource = sourceRepository.sources.first(where: {
                        $0.name == importedSource.name && $0.account?.id == accountId
                    })
                    let source = existingSource ?? sourceRepository.createSource(
                        name: importedSource.name,
                        category: category,
                        notificationFrequency: importedAccount.notificationFrequency,
                        customFrequencyMonths: importedAccount.customFrequencyMonths
                    )
                    source.account = account
                    if existingSource == nil {
                        sourcesCreated += 1
                    }

                    for snapshot in importedSource.snapshots {
                        // Check if a snapshot with the same date already exists for this source
                        let existingSnapshot = fetchSnapshot(for: source, date: snapshot.date, in: context)

                        if let existing = existingSnapshot {
                            // Update existing snapshot instead of creating a duplicate
                            existing.value = NSDecimalNumber(decimal: snapshot.value)
                            if let contribution = snapshot.contribution {
                                existing.contribution = NSDecimalNumber(decimal: contribution)
                            }
                            if let notes = snapshot.notes, !notes.isEmpty {
                                existing.notes = notes
                            }
                            snapshotsUpdated += 1
                        } else {
                            // Create new snapshot
                            snapshotRepository.createSnapshot(
                                for: source,
                                date: snapshot.date,
                                value: snapshot.value,
                                contribution: snapshot.contribution,
                                notes: snapshot.notes
                            )
                            snapshotsCreated += 1
                        }
                        snapshotProgress?(snapshotsCreated)

                        let monthKey = MonthlyCheckInStore.monthKey(for: snapshot.date)
                        if let existingDate = completionDatesByMonth[monthKey] {
                            if snapshot.date > existingDate {
                                completionDatesByMonth[monthKey] = snapshot.date
                            }
                        } else {
                            completionDatesByMonth[monthKey] = snapshot.date
                        }
                    }
                }
            }
        }

        if context.hasChanges {
            do {
                try context.save()
            } catch {
                errors.append("Failed to save imported data.")
            }
        }

        if !completionDatesByMonth.isEmpty {
            for (monthKey, completionDate) in completionDatesByMonth {
                let formatter = DateFormatter()
                formatter.dateFormat = "yyyy-MM"
                if let monthDate = formatter.date(from: monthKey) {
                    MonthlyCheckInStore.setCompletionDate(completionDate, for: monthDate)
                }
            }
        }

        return ImportResult(
            accountsCreated: accountsCreated,
            sourcesCreated: sourcesCreated,
            snapshotsCreated: snapshotsCreated,
            snapshotsUpdated: snapshotsUpdated,
            errors: errors
        )
    }

    private func resolveExistingCategory(
        named rawName: String,
        lookup: [String: Category]
    ) -> Category? {
        if let canonical = canonicalCategoryName(for: rawName) {
            let canonicalKey = normalizedCategoryName(canonical)
            if let match = lookup[canonicalKey] {
                return match
            }
        }
        return lookup[normalizedCategoryName(rawName)]
    }

    private func buildCategoryLookup(from categories: [Category]) -> [String: Category] {
        var lookup: [String: Category] = [:]
        for category in categories {
            lookup[normalizedCategoryName(category.name)] = category
        }
        return lookup
    }

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

    private func fetchAccountsLookup(in context: NSManagedObjectContext) -> [String: Account] {
        let request: NSFetchRequest<Account> = Account.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Account.createdAt, ascending: true)]
        let accounts = (try? context.fetch(request)) ?? []
        var lookup: [String: Account] = [:]
        for account in accounts {
            lookup[account.name] = account
        }
        return lookup
    }

    /// Fetch a snapshot for a given source and date (same day)
    private func fetchSnapshot(
        for source: InvestmentSource,
        date: Date,
        in context: NSManagedObjectContext
    ) -> Snapshot? {
        let request: NSFetchRequest<Snapshot> = Snapshot.fetchRequest()

        // Match snapshots on the same day (ignore time component)
        let calendar = Calendar.current
        let startOfDay = calendar.startOfDay(for: date)
        let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!

        request.predicate = NSPredicate(
            format: "source == %@ AND date >= %@ AND date < %@",
            source,
            startOfDay as NSDate,
            endOfDay as NSDate
        )
        request.fetchLimit = 1

        return try? context.fetch(request).first
    }

    private func canonicalCategoryName(for rawName: String) -> String? {
        let normalized = normalizedCategoryName(rawName)
        for mapping in categoryAliasMappings {
            if mapping.aliases.contains(where: { normalizedCategoryName($0) == normalized }) {
                return mapping.canonical
            }
        }
        return nil
    }

    private func normalizedCategoryName(_ value: String) -> String {
        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
        let normalized = trimmed.folding(
            options: [.diacriticInsensitive, .caseInsensitive],
            locale: .current
        )
        return normalized.replacingOccurrences(
            of: "\\s+",
            with: " ",
            options: .regularExpression
        )
    }

    private var categoryAliasMappings: [(canonical: String, aliases: [String])] {
        [
            (
                canonical: "Stocks",
                aliases: ["Stocks", "category_stocks", String(localized: "category_stocks"), "Acciones"]
            ),
            (
                canonical: "Bonds",
                aliases: ["Bonds", "category_bonds", String(localized: "category_bonds"), "Bonos"]
            ),
            (
                canonical: "Real Estate",
                aliases: ["Real Estate", "category_real_estate", String(localized: "category_real_estate"), "Inmobiliario"]
            ),
            (
                canonical: "Crypto",
                aliases: ["Crypto", "category_crypto", String(localized: "category_crypto"), "Cripto"]
            ),
            (
                canonical: "Cash",
                aliases: ["Cash", "category_cash", String(localized: "category_cash"), "Efectivo"]
            ),
            (
                canonical: "ETFs",
                aliases: ["ETFs", "category_etfs", String(localized: "category_etfs"), "ETF"]
            ),
            (
                canonical: "Retirement",
                aliases: ["Retirement", "category_retirement", String(localized: "category_retirement"), "Jubilación"]
            ),
            (
                canonical: "Other",
                aliases: [
                    "Other",
                    "category_other",
                    String(localized: "category_other"),
                    "Uncategorized",
                    "uncategorized",
                    String(localized: "uncategorized"),
                    "Otros",
                    "Sin categoría"
                ]
            )
        ]
    }

    // MARK: - CSV Column Mapping

    /// Parsed preview data for the mapping UI
    struct CSVPreview {
        let rows: [[String]]
        let columnCount: Int

        func headers(hasHeaderRow: Bool) -> [String] {
            if hasHeaderRow, let first = rows.first {
                return first
            }
            return (0..<columnCount).map { "Column \($0 + 1)" }
        }

        func sampleRow(hasHeaderRow: Bool) -> [String]? {
            let start = hasHeaderRow ? 1 : 0
            return rows.count > start ? rows[start] : nil
        }
    }

    func previewCSV(_ content: String) -> CSVPreview {
        let rows = parseCSVRows(content)
        let columnCount = rows.first?.count ?? 0
        return CSVPreview(rows: rows, columnCount: columnCount)
    }

    // Maps each app field to a CSV column index or a constant value
    struct CSVMappingConfig {
        // -1 = not mapped, -2 = constant, -3 = use today (date only), 0+ = column index
        static let notMapped = -1
        static let constant = -2
        static let useToday = -3

        var hasHeaderRow: Bool = true
        var sourceIndex: Int = notMapped
        var sourceConstant: String = ""
        var valueIndex: Int = notMapped
        var dateIndex: Int = useToday          // default: use today
        var categoryIndex: Int = constant
        var categoryConstant: String = "Other" // default category name when constant
        var contributionIndex: Int = notMapped
        var notesIndex: Int = notMapped

        var isValid: Bool {
            let sourceOk: Bool = {
                if sourceIndex == Self.constant { return !sourceConstant.isEmpty }
                return sourceIndex >= 0
            }()
            let valueOk = valueIndex >= 0
            return sourceOk && valueOk
        }
    }

    func importCSVWithMapping(
        content: String,
        mapping: CSVMappingConfig,
        defaultAccountName: String?
    ) -> ImportResult {
        let preview = previewCSV(content)
        let accounts = parseCSVWithMapping(preview: preview, mapping: mapping, defaultAccountName: defaultAccountName)
        return applyImport(accounts, context: CoreDataStack.shared.viewContext)
    }

    func importCSVWithMappingAsync(
        content: String,
        mapping: CSVMappingConfig,
        defaultAccountName: String?,
        progress: @escaping (ImportProgress) -> Void
    ) async -> ImportResult {
        await withCheckedContinuation { continuation in
            CoreDataStack.shared.performBackgroundTask { context in
                let preview = self.previewCSV(content)
                let accounts = self.parseCSVWithMapping(preview: preview, mapping: mapping, defaultAccountName: defaultAccountName)

                let totalSnapshots = accounts.reduce(0) { t, a in
                    t + a.categories.reduce(0) { s, c in s + c.sources.reduce(0) { $0 + $1.snapshots.count } }
                }
                DispatchQueue.main.async {
                    progress(ImportProgress(completed: 0, total: totalSnapshots, message: "Importing data"))
                }

                let result = self.applyImport(accounts, context: context) { completed in
                    DispatchQueue.main.async {
                        progress(ImportProgress(
                            completed: completed,
                            total: totalSnapshots,
                            message: "Imported \(completed) of \(totalSnapshots) snapshots"
                        ))
                    }
                }
                continuation.resume(returning: result)
            }
        }
    }

    private func parseCSVWithMapping(
        preview: CSVPreview,
        mapping: CSVMappingConfig,
        defaultAccountName: String?
    ) -> [ImportedAccount] {
        let dataRows = mapping.hasHeaderRow ? Array(preview.rows.dropFirst()) : preview.rows
        let fallbackAccount = defaultAccountName ?? "Personal"

        var grouped: [String: [String: [String: [ImportedSnapshot]]]] = [:]

        for row in dataRows {
            // Source name
            let sourceName: String
            if mapping.sourceIndex == CSVMappingConfig.constant {
                guard !mapping.sourceConstant.isEmpty else { continue }
                sourceName = mapping.sourceConstant
            } else if mapping.sourceIndex >= 0 {
                guard let v = row.safeValue(at: mapping.sourceIndex), !v.isEmpty else { continue }
                sourceName = v
            } else {
                continue
            }

            // Value
            guard mapping.valueIndex >= 0,
                  let valStr = row.safeValue(at: mapping.valueIndex),
                  let value = parseDecimal(valStr) else { continue }

            // Date
            let date: Date
            if mapping.dateIndex == CSVMappingConfig.useToday {
                date = Calendar.current.startOfDay(for: Date())
            } else if mapping.dateIndex >= 0,
                      let dateStr = row.safeValue(at: mapping.dateIndex),
                      let parsed = parseDate(dateStr) {
                date = parsed
            } else {
                date = Calendar.current.startOfDay(for: Date())
            }

            // Category
            let categoryName: String
            if mapping.categoryIndex == CSVMappingConfig.constant {
                categoryName = mapping.categoryConstant.isEmpty ? "Other" : mapping.categoryConstant
            } else if mapping.categoryIndex >= 0,
                      let v = row.safeValue(at: mapping.categoryIndex), !v.isEmpty {
                categoryName = v
            } else {
                categoryName = "Other"
            }

            // Contribution
            let contribution: Decimal? = mapping.contributionIndex >= 0
                ? row.safeValue(at: mapping.contributionIndex).flatMap(parseDecimal)
                : nil

            // Notes
            let notes: String? = mapping.notesIndex >= 0
                ? row.safeValue(at: mapping.notesIndex).flatMap { $0.isEmpty ? nil : $0 }
                : nil

            let snapshot = ImportedSnapshot(date: date, value: value, contribution: contribution, notes: notes)
            grouped[fallbackAccount, default: [:]][categoryName, default: [:]][sourceName, default: []].append(snapshot)
        }

        return grouped.map { accountName, categories in
            let importedCategories = categories.map { categoryName, sources in
                let importedSources = sources.map { sourceName, snapshots in
                    ImportedSource(name: sourceName, snapshots: snapshots)
                }
                return ImportedCategory(name: categoryName, colorHex: nil, icon: nil, sources: importedSources)
            }
            return ImportedAccount(
                name: accountName,
                currency: nil,
                inputMode: .simple,
                notificationFrequency: .monthly,
                customFrequencyMonths: 1,
                categories: importedCategories
            )
        }
    }

    // MARK: - CSV Helpers

    private func parseCSVRows(_ content: String) -> [[String]] {
        var rows: [[String]] = []
        var currentRow: [String] = []
        var currentField = ""
        var insideQuotes = false

        for char in content {
            if char == "\"" {
                insideQuotes.toggle()
                continue
            }

            if char == "," && !insideQuotes {
                currentRow.append(currentField)
                currentField = ""
                continue
            }

            if char == "\n" && !insideQuotes {
                currentRow.append(currentField)
                rows.append(currentRow.map { $0.trimmingCharacters(in: .whitespaces) })
                currentRow = []
                currentField = ""
                continue
            }

            currentField.append(char)
        }

        if !currentField.isEmpty || !currentRow.isEmpty {
            currentRow.append(currentField)
            rows.append(currentRow.map { $0.trimmingCharacters(in: .whitespaces) })
        }

        return rows
    }

    private func parseDate(_ value: String) -> Date? {
        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
        if trimmed.isEmpty { return nil }

        if let iso = ISO8601DateFormatter().date(from: trimmed) {
            return iso
        }

        let formats = [
            "yyyy-MM-dd",
            "yyyy/MM/dd",
            "dd/MM/yyyy",
            "MM/dd/yyyy",
            "dd-MM-yyyy",
            "MM-dd-yyyy",
            "yyyy-MM-dd HH:mm",
            "yyyy-MM-dd HH:mm:ss",
            "yyyy/MM/dd HH:mm",
            "yyyy/MM/dd HH:mm:ss",
            "dd/MM/yyyy HH:mm",
            "dd/MM/yyyy HH:mm:ss",
            "MM/dd/yyyy HH:mm",
            "MM/dd/yyyy HH:mm:ss",
            "dd-MM-yyyy HH:mm",
            "dd-MM-yyyy HH:mm:ss",
            "MM-dd-yyyy HH:mm",
            "MM-dd-yyyy HH:mm:ss",
            "dd/MM/yyyy h:mm a",
            "dd/MM/yyyy h:mm:ss a",
            "MM/dd/yyyy h:mm a",
            "MM/dd/yyyy h:mm:ss a",
            "dd-MM-yyyy h:mm a",
            "dd-MM-yyyy h:mm:ss a",
            "MM-dd-yyyy h:mm a",
            "MM-dd-yyyy h:mm:ss a"
        ]

        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = .current

        for format in formats {
            formatter.dateFormat = format
            if let date = formatter.date(from: trimmed) {
                return date
            }
        }

        return nil
    }

    private func parseDecimal(_ value: String) -> Decimal? {
        let cleaned = value
            .replacingOccurrences(of: ",", with: ".")
            .trimmingCharacters(in: .whitespaces)
        guard !cleaned.isEmpty else { return nil }
        return Decimal(string: cleaned)
    }
}

private extension Array where Element == String {
    func safeValue(at index: Int) -> String? {
        guard index >= 0, index < count else { return nil }
        return self[index]
    }
}
