import Foundation
import Combine
import CoreData
import SwiftUI

@MainActor
class DashboardViewModel: ObservableObject {
    // MARK: - Published Properties

    @Published var portfolioSummary: PortfolioSummary = .empty
    @Published var monthlySummary: MonthlySummary = .empty
    @Published var categoryMetrics: [CategoryMetrics] = []
    @Published var categoryEvolutionData: [CategoryEvolutionPoint] = []
    @Published var recentSnapshots: [Snapshot] = []
    @Published var sourcesNeedingUpdate: [InvestmentSource] = []
    @Published var latestPortfolioChange: PortfolioChange = .empty
    @Published var isLoading = false
    @Published var errorMessage: String?
    @Published var selectedAccount: Account?
    @Published var showAllAccounts = true
    @Published var showingPaywall = false

    // MARK: - Chart Data

    @Published var evolutionData: [(date: Date, value: Decimal)] = []
    @Published var updateStreak: Int = 0

    // MARK: - Portfolio Forecast

    @Published var portfolioForecast: PortfolioForecast?

    // MARK: - Dependencies

    private let categoryRepository: CategoryRepository
    private let sourceRepository: InvestmentSourceRepository
    private let snapshotRepository: SnapshotRepository
    private let calculationService: CalculationService
    private let predictionEngine: PredictionEngine
    private var cancellables = Set<AnyCancellable>()
    private var isRefreshing = false
    private var refreshQueued = false
    private var refreshTask: Task<Void, Never>?
    private let maxHistoryMonths = 60

    // MARK: - Performance: Caching
    private var cachedFilteredSources: [InvestmentSource]?
    private var cachedSourcesHash: Int = 0
    private var lastAccountId: UUID?
    private var lastShowAllAccounts: Bool = true

    // MARK: - Initialization

    init(
        categoryRepository: CategoryRepository? = nil,
        sourceRepository: InvestmentSourceRepository? = nil,
        snapshotRepository: SnapshotRepository? = nil,
        calculationService: CalculationService? = nil
    ) {
        self.categoryRepository = categoryRepository ?? CategoryRepository()
        self.sourceRepository = sourceRepository ?? InvestmentSourceRepository()
        self.snapshotRepository = snapshotRepository ?? SnapshotRepository()
        self.calculationService = calculationService ?? .shared
        self.predictionEngine = .shared

        setupObservers()
        loadData()
    }

    // MARK: - Setup

    private func setupObservers() {
        // Performance: Combine multiple publishers to reduce redundant refresh calls
        // Use dropFirst to avoid initial trigger, and debounce to coalesce rapid changes
        Publishers.Merge(
            categoryRepository.$categories.map { _ in () },
            sourceRepository.$sources.map { _ in () }
        )
        .dropFirst(2) // Skip initial values from both publishers
        .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
        .sink { [weak self] _ in
            self?.invalidateCache()
            self?.refreshData()
        }
        .store(in: &cancellables)

        // Observe Core Data changes with coalescing
        NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange)
            .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
            .compactMap { [weak self] notification -> Void? in
                guard let self, self.isRelevantChange(notification) else { return nil }
                return ()
            }
            .sink { [weak self] _ in
                self?.invalidateCache()
                self?.refreshData()
            }
            .store(in: &cancellables)
    }

    private func isRelevantChange(_ notification: Notification) -> Bool {
        guard let info = notification.userInfo else { return false }
        let keys: [String] = [
            NSInsertedObjectsKey,
            NSUpdatedObjectsKey,
            NSDeletedObjectsKey,
            NSRefreshedObjectsKey
        ]

        for key in keys {
            if let objects = info[key] as? Set<NSManagedObject> {
                if objects.contains(where: { $0 is Snapshot || $0 is InvestmentSource || $0 is Category }) {
                    return true
                }
            }
        }
        return false
    }

    // MARK: - Data Loading

    func loadData() {
        isLoading = true
        errorMessage = nil

        queueRefresh(updateLoadingFlag: true)
    }

    func refreshData() {
        queueRefresh(updateLoadingFlag: false)
    }

    private func queueRefresh(updateLoadingFlag: Bool) {
        refreshQueued = true
        if updateLoadingFlag {
            isLoading = true
        }

        guard !isRefreshing else { return }

        isRefreshing = true

        // Cancel any previous refresh task to avoid stale updates
        refreshTask?.cancel()

        refreshTask = Task { [weak self] in
            guard let self else { return }
            while self.refreshQueued && !Task.isCancelled {
                self.refreshQueued = false
                await self.refreshAllData()
            }
            self.isRefreshing = false
            if updateLoadingFlag {
                self.isLoading = false
            }
        }
    }

    /// Cancel any ongoing background tasks - call when view disappears
    func cancelPendingTasks() {
        refreshTask?.cancel()
        refreshTask = nil
    }

    private func refreshAllData() async {
        let categories = categoryRepository.categories
        let sources = filteredSources()
        let allSnapshots = filteredSnapshots(for: sources)

        // Calculate portfolio summary
        portfolioSummary = calculationService.calculatePortfolioSummary(
            from: sources,
            snapshots: allSnapshots
        )

        monthlySummary = calculationService.calculateMonthlySummary(
            sources: sources,
            snapshots: allSnapshots
        )

        // Calculate category metrics
        categoryMetrics = calculationService.calculateCategoryMetrics(
            for: categories,
            sources: sources,
            totalPortfolioValue: portfolioSummary.totalValue
        ).sorted { $0.totalValue > $1.totalValue }

        // Get recent snapshots
        recentSnapshots = Array(allSnapshots.prefix(10))

        // Get sources needing update
        let accountFilter = showAllAccounts ? nil : selectedAccount
        sourcesNeedingUpdate = sourceRepository.fetchSourcesNeedingUpdate(for: accountFilter)

        // Calculate evolution data for chart (only completed monthly check-ins)
        let completedSnapshots = filterSnapshotsForCharts(
            sources: sources,
            snapshots: allSnapshots
        )
        updateEvolutionData(from: completedSnapshots, categories: categories)
        latestPortfolioChange = calculateLatestCheckInChange(
            sources: sources,
            snapshots: allSnapshots,
            fallback: evolutionData
        )

        // Calculate portfolio forecast
        updatePortfolioForecast()

        // Compute update streak
        updateStreak = computeStreak(from: evolutionData)

        // Log screen view
        FirebaseService.shared.logScreenView(screenName: "Dashboard")
    }

    private func filteredSources() -> [InvestmentSource] {
        let safeSelectedAccountId = selectedAccount?.safeId

        // Performance: Cache filtered sources to avoid repeated filtering
        let currentHash = sourceRepository.sources.count
        let accountChanged = lastAccountId != safeSelectedAccountId || lastShowAllAccounts != showAllAccounts

        if !accountChanged && cachedFilteredSources != nil && cachedSourcesHash == currentHash {
            return cachedFilteredSources!
        }

        lastAccountId = safeSelectedAccountId
        lastShowAllAccounts = showAllAccounts
        cachedSourcesHash = currentHash

        if showAllAccounts || safeSelectedAccountId == nil {
            cachedFilteredSources = sourceRepository.sources
        } else {
            cachedFilteredSources = sourceRepository.sources.filter { $0.account?.id == safeSelectedAccountId }
        }
        return cachedFilteredSources!
    }

    /// Invalidates cached data when underlying data changes
    private func invalidateCache() {
        cachedFilteredSources = nil
        cachedSourcesHash = 0
    }

    private func filteredSnapshots(for sources: [InvestmentSource]) -> [Snapshot] {
        let sourceIds = sources.compactMap { $0.id }
        return snapshotRepository.fetchSnapshots(
            for: sourceIds,
            months: maxHistoryMonths
        )
    }

    private func updateEvolutionData(from snapshots: [Snapshot], categories: [Category]) {
        let summary = calculateEvolutionSummary(from: snapshots)
        let categoriesWithData = Set(summary.categoryTotals.keys)
        let categoryLookup = Dictionary(uniqueKeysWithValues: categories.map { ($0.id, $0) })

        evolutionData = summary.evolutionData
        categoryEvolutionData = summary.categorySeries.flatMap { entry in
            entry.valuesByCategory.compactMap { categoryId, value in
                guard categoriesWithData.contains(categoryId),
                      let category = categoryLookup[categoryId] else {
                    return nil
                }
                return CategoryEvolutionPoint(
                    date: entry.date,
                    categoryName: category.name,
                    colorHex: category.colorHex,
                    value: value
                )
            }
        }
    }

    private struct EvolutionSummary {
        let evolutionData: [(date: Date, value: Decimal)]
        let categorySeries: [(date: Date, valuesByCategory: [UUID: Decimal])]
        let categoryTotals: [UUID: Decimal]
    }

    private func calculateEvolutionSummary(from snapshots: [Snapshot]) -> EvolutionSummary {
        guard !snapshots.isEmpty else {
            return EvolutionSummary(evolutionData: [], categorySeries: [], categoryTotals: [:])
        }

        let sortedSnapshots = snapshots.sorted { $0.date < $1.date }
        let groupedByMonth = Dictionary(grouping: sortedSnapshots) { snapshot -> DateComponents in
            let components = Calendar.current.dateComponents([.year, .month], from: snapshot.date)
            return DateComponents(year: components.year, month: components.month)
        }

        var evolution: [(date: Date, value: Decimal)] = []
        evolution.reserveCapacity(groupedByMonth.count)
        var series: [(date: Date, valuesByCategory: [UUID: Decimal])] = []
        series.reserveCapacity(groupedByMonth.count)
        var categoryTotals: [UUID: Decimal] = [:]

        for (key, monthSnapshots) in groupedByMonth {
            var latestBySource: [UUID: Snapshot] = [:]
            for snapshot in monthSnapshots {
                guard let sourceId = snapshot.source?.id else { continue }
                if let existing = latestBySource[sourceId] {
                    if snapshot.date > existing.date {
                        latestBySource[sourceId] = snapshot
                    }
                } else {
                    latestBySource[sourceId] = snapshot
                }
            }

            var total: Decimal = 0
            var valuesByCategory: [UUID: Decimal] = [:]

            for snapshot in latestBySource.values {
                let value = snapshot.decimalValue
                total += value
                if let categoryId = snapshot.source?.category?.id {
                    valuesByCategory[categoryId, default: 0] += value
                    categoryTotals[categoryId, default: 0] += value
                }
            }

            let date = Calendar.current.date(from: key) ?? Date()
            evolution.append((date: date, value: total))
            series.append((date: date, valuesByCategory: valuesByCategory))
        }

        evolution.sort { $0.date < $1.date }
        series.sort { $0.date < $1.date }

        return EvolutionSummary(
            evolutionData: evolution,
            categorySeries: series,
            categoryTotals: categoryTotals
        )
    }

    private func completedMonthKeys(
        sources: [InvestmentSource],
        snapshots: [Snapshot],
        after cutoff: Date
    ) -> Set<DateComponents> {
        let sourceIds = Set(sources.compactMap { $0.id })
        guard !sourceIds.isEmpty else { return [] }

        let groupedByMonth = Dictionary(grouping: snapshots) { snapshot -> DateComponents in
            let components = Calendar.current.dateComponents([.year, .month], from: snapshot.date)
            return DateComponents(year: components.year, month: components.month)
        }

        var completed: Set<DateComponents> = []
        completed.reserveCapacity(groupedByMonth.count)

        for (key, monthSnapshots) in groupedByMonth {
            guard let monthDate = Calendar.current.date(from: key) else { continue }
            guard monthDate > cutoff else { continue }
            // A month is "complete" if all active sources have snapshot data for it.
            // We intentionally do NOT require a MonthlyCheckInStore entry here because
            // that store lives in UserDefaults (device-local) and is not synced via iCloud.
            // Snapshot data from CoreData is the authoritative cross-device source of truth.
            let monthSourceIds = Set(monthSnapshots.compactMap { $0.source?.id })
            if sourceIds.isSubset(of: monthSourceIds) {
                completed.insert(key)
            }
        }

        return completed
    }

    private func filterSnapshotsForCharts(
        sources: [InvestmentSource],
        snapshots: [Snapshot]
    ) -> [Snapshot] {
        guard let lastCompleted = MonthlyCheckInStore.latestCompletionDate()?.startOfMonth else {
            return snapshots
        }

        let completedMonthsAfter = completedMonthKeys(
            sources: sources,
            snapshots: snapshots,
            after: lastCompleted
        )

        return snapshots.filter { snapshot in
            let monthDate = snapshot.date.startOfMonth
            if let completionDate = MonthlyCheckInStore.completionDate(for: monthDate),
               snapshot.date > completionDate {
                return false
            }
            if monthDate <= lastCompleted {
                return true
            }
            let components = Calendar.current.dateComponents([.year, .month], from: snapshot.date)
            let key = DateComponents(year: components.year, month: components.month)
            return completedMonthsAfter.contains(key)
        }
    }

    private func calculateLatestChange(from data: [(date: Date, value: Decimal)]) -> PortfolioChange {
        guard data.count >= 2 else {
            return PortfolioChange(absolute: 0, percentage: 0, label: "since last check-in")
        }
        let last = data[data.count - 1]
        let previous = data[data.count - 2]
        let absolute = last.value - previous.value
        let percentage = previous.value > 0
            ? NSDecimalNumber(decimal: absolute / previous.value).doubleValue * 100
            : 0
        return PortfolioChange(absolute: absolute, percentage: percentage, label: "since last check-in")
    }

    private func calculateLatestCheckInChange(
        sources: [InvestmentSource],
        snapshots: [Snapshot],
        fallback: [(date: Date, value: Decimal)]
    ) -> PortfolioChange {
        let completedMonths = MonthlyCheckInStore.allEntries()
            .compactMap { entry -> Date? in
                entry.entry.completionDate != nil ? entry.date.startOfMonth : nil
            }
            .sorted()

        guard completedMonths.count >= 2 else {
            return calculateLatestChange(from: fallback)
        }

        let lastMonth = completedMonths[completedMonths.count - 1]
        let previousMonth = completedMonths[completedMonths.count - 2]
        let lastCompletion = MonthlyCheckInStore.completionDate(for: lastMonth) ?? lastMonth.endOfMonth
        let previousCompletion = MonthlyCheckInStore.completionDate(for: previousMonth) ?? previousMonth.endOfMonth

        let lastValue = totalPortfolioValue(asOf: lastCompletion, sources: sources, snapshots: snapshots)
        let previousValue = totalPortfolioValue(asOf: previousCompletion, sources: sources, snapshots: snapshots)
        let absolute = lastValue - previousValue
        let percentage = previousValue > 0
            ? NSDecimalNumber(decimal: absolute / previousValue).doubleValue * 100
            : 0

        return PortfolioChange(absolute: absolute, percentage: percentage, label: "since last check-in")
    }

    private func totalPortfolioValue(
        asOf date: Date,
        sources: [InvestmentSource],
        snapshots: [Snapshot]
    ) -> Decimal {
        let snapshotsBySource = Dictionary(grouping: snapshots) { $0.source?.id }
        var total = Decimal.zero

        for source in sources {
            let sourceId = source.id
            guard let sourceSnapshots = snapshotsBySource[sourceId] else { continue }
            if let latest = sourceSnapshots
                .filter({ $0.date <= date })
                .max(by: { $0.date < $1.date }) {
                total += latest.decimalValue
            }
        }

        return total
    }

    private func updatePortfolioForecast() {
        // Need at least 3 data points for meaningful forecast
        guard evolutionData.count >= 3 else {
            portfolioForecast = nil
            return
        }

        let currentValue = evolutionData.last?.value ?? 0

        // Use the evolution data to predict 12 months ahead
        // Create "virtual snapshots" from evolution data for the prediction engine
        let virtualSnapshots = evolutionData.map { dataPoint -> VirtualSnapshot in
            VirtualSnapshot(date: dataPoint.date, value: dataPoint.value)
        }

        // Calculate using Holt-Winters or linear trend based on data volatility
        let values = virtualSnapshots.map { NSDecimalNumber(decimal: $0.value).doubleValue }
        guard values.count >= 3 else {
            portfolioForecast = nil
            return
        }

        // Calculate monthly growth rate from recent data
        let recentData = Array(virtualSnapshots.suffix(min(12, virtualSnapshots.count)))
        guard let firstPoint = recentData.first, let lastPoint = recentData.last,
              firstPoint.value > 0 else {
            portfolioForecast = nil
            return
        }

        let monthsBetween = max(1, firstPoint.date.monthsBetween(lastPoint.date))
        let totalGrowth = lastPoint.value - firstPoint.value
        let monthlyGrowth = totalGrowth / Decimal(monthsBetween)

        // Project 12 months ahead using compound growth (matches CAGR semantics);
        // fall back to the linear projection if the ratio is degenerate.
        let firstD = NSDecimalNumber(decimal: firstPoint.value).doubleValue
        let lastD = NSDecimalNumber(decimal: lastPoint.value).doubleValue
        let currentD = NSDecimalNumber(decimal: currentValue).doubleValue
        let forecastValue: Decimal
        if firstD > 0, lastD > 0 {
            let monthlyRate = pow(lastD / firstD, 1.0 / Double(monthsBetween))
            let projected = currentD * pow(monthlyRate, 12)
            forecastValue = projected.isFinite ? Decimal(projected) : currentValue + (monthlyGrowth * 12)
        } else {
            forecastValue = currentValue + (monthlyGrowth * 12)
        }

        // Calculate confidence interval based on volatility
        let volatility = calculatePortfolioVolatility(from: values)
        let confidenceWidth = NSDecimalNumber(decimal: currentValue).doubleValue * volatility * 0.5

        portfolioForecast = PortfolioForecast(
            currentValue: currentValue,
            forecastValue: forecastValue,
            forecastDate: Calendar.current.date(byAdding: .month, value: 12, to: Date()) ?? Date(),
            confidenceLower: Decimal(max(0, NSDecimalNumber(decimal: forecastValue).doubleValue - confidenceWidth)),
            confidenceUpper: forecastValue + Decimal(confidenceWidth),
            monthlyGrowthRate: monthlyGrowth,
            annualizedGrowthRate: calculateAnnualizedGrowth(from: virtualSnapshots)
        )
    }

    private func calculatePortfolioVolatility(from values: [Double]) -> Double {
        guard values.count >= 2 else { return 0 }

        var returns: [Double] = []
        for i in 1..<values.count {
            guard values[i - 1] != 0 else { continue }
            let periodReturn = (values[i] - values[i - 1]) / values[i - 1]
            returns.append(periodReturn)
        }

        guard !returns.isEmpty else { return 0 }

        let mean = returns.reduce(0, +) / Double(returns.count)
        let squaredDiffs = returns.map { pow($0 - mean, 2) }
        let variance = squaredDiffs.reduce(0, +) / Double(max(1, returns.count - 1))
        return sqrt(variance)
    }

    private func calculateAnnualizedGrowth(from snapshots: [VirtualSnapshot]) -> Decimal {
        guard let first = snapshots.first, let last = snapshots.last,
              first.value > 0 else { return 0 }

        let monthsBetween = max(1, first.date.monthsBetween(last.date))
        let totalReturn = (last.value - first.value) / first.value

        // Annualize compounding: (1 + total_return)^(12/months) - 1.
        // The previous linear approximation (monthly*12) overstated short histories.
        let totalReturnD = NSDecimalNumber(decimal: totalReturn).doubleValue
        guard totalReturnD > -1 else { return 0 }
        let annualizedD = pow(1 + totalReturnD, 12.0 / Double(monthsBetween)) - 1
        guard annualizedD.isFinite else { return 0 }
        return Decimal(annualizedD)
    }

    private func computeStreak(from evolutionData: [(date: Date, value: Decimal)]) -> Int {
        guard !evolutionData.isEmpty else { return 0 }
        let calendar = Calendar.current

        // Group into (year, month) set
        let monthSet: Set<DateComponents> = Set(evolutionData.map { point in
            let comps = calendar.dateComponents([.year, .month], from: point.date)
            return DateComponents(year: comps.year, month: comps.month)
        })

        // Get sorted unique months descending
        let sortedMonths = monthSet
            .compactMap { comps -> Date? in calendar.date(from: comps) }
            .sorted(by: >)

        guard let mostRecent = sortedMonths.first else { return 0 }

        var streak = 1
        var current = mostRecent

        for i in 1..<sortedMonths.count {
            guard let expected = calendar.date(byAdding: .month, value: -1, to: current) else { break }
            let prev = sortedMonths[i]
            let prevComps = calendar.dateComponents([.year, .month], from: prev)
            let expectedComps = calendar.dateComponents([.year, .month], from: expected)
            if prevComps.year == expectedComps.year && prevComps.month == expectedComps.month {
                streak += 1
                current = prev
            } else {
                break
            }
        }

        return streak
    }

    // Virtual snapshot for forecast calculations
    private struct VirtualSnapshot {
        let date: Date
        let value: Decimal
    }

    func goalEtaText(for goal: Goal, currentValue: Decimal) -> String? {
        let target = goal.targetDecimal
        let lastValue = evolutionData.last?.value ?? currentValue

        if currentValue >= target {
            return "Goal reached. Keep it steady."
        }

        guard let months = estimateMonthsToGoal(target: target, lastValue: lastValue) else {
            return "Keep going. Consistency pays off."
        }

        if months == 0 {
            return "Almost there. One more check-in."
        }

        let baseDate = evolutionData.last?.date ?? Date()
        let estimatedDate = Calendar.current.date(byAdding: .month, value: months, to: baseDate)
        if months >= 72 {
            return "Long journey, strong discipline. You're building momentum."
        }

        if let estimatedDate = estimatedDate {
            return "Estimated: \(estimatedDate.monthYearString)"
        }

        return "Estimated: \(months) months"
    }

    private func estimateMonthsToGoal(target: Decimal, lastValue: Decimal) -> Int? {
        guard evolutionData.count >= 3 else { return nil }
        let recent = Array(evolutionData.suffix(6))
        guard let first = recent.first, let last = recent.last else { return nil }
        let monthsBetween = max(1, first.date.monthsBetween(last.date))
        let delta = last.value - first.value
        guard delta > 0 else { return nil }
        let monthlyGain = delta / Decimal(monthsBetween)
        guard monthlyGain > 0 else { return nil }
        let remaining = target - lastValue
        guard remaining > 0 else { return 0 }
        let months = NSDecimalNumber(decimal: remaining / monthlyGain).doubleValue
        return Int(ceil(months))
    }

    // MARK: - Computed Properties

    var hasData: Bool {
        !filteredSources().isEmpty
    }

    var totalSourceCount: Int {
        sourceRepository.sourceCount
    }

    var totalCategoryCount: Int {
        categoryRepository.categories.count
    }

    var pendingUpdatesCount: Int {
        sourcesNeedingUpdate.count
    }

    var insights: [PortfolioInsight] {
        var result: [PortfolioInsight] = []
        guard portfolioSummary.totalValue > 0 else { return result }
        let total = portfolioSummary.totalValue

        // 1. Milestone approaching (within 10% of next round milestone)
        let milestones: [Decimal] = [1000, 2500, 5000, 10000, 25000, 50000,
                                      100000, 250000, 500000, 1_000_000,
                                      2_500_000, 5_000_000, 10_000_000]
        if let next = milestones.first(where: { $0 > total }) {
            let pct = NSDecimalNumber(decimal: total / next).doubleValue
            if pct >= 0.90 {
                let gap = next - total
                let gapStr = CurrencyFormatter.format(gap, style: .currency, maximumFractionDigits: 0)
                let msStr = CurrencyFormatter.format(next, style: .currency, maximumFractionDigits: 0)
                result.append(PortfolioInsight(
                    id: "milestone",
                    systemImage: "flag.checkered",
                    title: String(localized: "insight_milestone_title"),
                    value: String(format: String(localized: "insight_milestone_value"), gapStr, msStr),
                    accentColor: .orange
                ))
            }
        }

        // 2. Year-to-date performance
        let ytdPct = portfolioSummary.yearChangePercentage
        if abs(ytdPct) >= 0.1 {
            let icon = ytdPct >= 0 ? "arrow.up.right.circle.fill" : "arrow.down.right.circle.fill"
            result.append(PortfolioInsight(
                id: "ytd",
                systemImage: icon,
                title: String(localized: "insight_ytd_title"),
                value: String(format: "%+.1f%%", ytdPct),
                accentColor: ytdPct >= 0 ? .positiveGreen : .negativeRed
            ))
        }

        // 3. All-time market gains
        let gains = portfolioSummary.allTimeReturn
        if gains > 0 {
            let gainStr = CurrencyFormatter.format(gains, style: .currency, maximumFractionDigits: 0)
            result.append(PortfolioInsight(
                id: "market_gains",
                systemImage: "chart.line.uptrend.xyaxis",
                title: String(localized: "insight_market_gains_title"),
                value: String(format: String(localized: "insight_market_gains_value"), gainStr),
                accentColor: .appPrimary
            ))
        }

        // 4. Tracking streak (>= 3 months)
        if updateStreak >= 3 {
            result.append(PortfolioInsight(
                id: "streak",
                systemImage: "flame.fill",
                title: String(localized: "insight_streak_title"),
                value: String(format: String(localized: "insight_streak_value"), updateStreak),
                accentColor: .orange
            ))
        }

        // 5. Forecast
        if let forecast = portfolioForecast, forecast.forecastValue > total {
            result.append(PortfolioInsight(
                id: "forecast",
                systemImage: "wand.and.stars",
                title: String(localized: "insight_forecast_title"),
                value: "\(forecast.formattedForecastValue) · \(forecast.formattedForecastDate)",
                accentColor: .purple
            ))
        }

        return result
    }

    var topCategories: [CategoryMetrics] {
        Array(categoryMetrics.prefix(5))
    }

    // MARK: - Formatting

    var formattedTotalValue: String {
        portfolioSummary.formattedTotalValue
    }

    var formattedDayChange: String {
        portfolioSummary.formattedDayChange
    }

    var formattedMonthChange: String {
        portfolioSummary.formattedMonthChange
    }

    var formattedYearChange: String {
        portfolioSummary.formattedYearChange
    }

    var isDayChangePositive: Bool {
        portfolioSummary.dayChange >= 0
    }

    var isMonthChangePositive: Bool {
        portfolioSummary.monthChange >= 0
    }

    var isYearChangePositive: Bool {
        portfolioSummary.yearChange >= 0
    }

    var formattedLastUpdate: String {
        portfolioSummary.lastUpdated?.friendlyDescription ?? "Not yet"
    }
}
