import Foundation
import Combine
import CoreData

@MainActor
class GoalsViewModel: ObservableObject {
    @Published var goals: [Goal] = []
    @Published var totalValue: Decimal = Decimal.zero
    @Published var selectedAccount: Account?
    @Published var showAllAccounts = true

    private let goalRepository: GoalRepository
    private let sourceRepository: InvestmentSourceRepository
    private let snapshotRepository: SnapshotRepository
    private let maxHistoryMonths = 60
    private var cancellables = Set<AnyCancellable>()

    // MARK: - Performance: Caching
    private var cachedEvolutionData: [UUID: [(date: Date, value: Decimal)]] = [:]
    private var cachedCompletionDates: [UUID: Date?] = [:]
    private var lastSourcesHash: Int = 0

    init(
        goalRepository: GoalRepository? = nil,
        sourceRepository: InvestmentSourceRepository? = nil,
        snapshotRepository: SnapshotRepository? = nil
    ) {
        self.goalRepository = goalRepository ?? GoalRepository()
        self.sourceRepository = sourceRepository ?? InvestmentSourceRepository()
        self.snapshotRepository = snapshotRepository ?? SnapshotRepository()

        setupObservers()
        refresh()
    }

    private func setupObservers() {
        goalRepository.$goals
            .receive(on: DispatchQueue.main)
            .sink { [weak self] goals in
                self?.updateGoals(using: goals)
            }
            .store(in: &cancellables)

        sourceRepository.$sources
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.updateTotalValue()
            }
            .store(in: &cancellables)
    }

    func refresh() {
        // Performance: Invalidate caches when refreshing
        let currentHash = sourceRepository.sources.count
        if currentHash != lastSourcesHash {
            cachedEvolutionData.removeAll()
            cachedCompletionDates.removeAll()
            lastSourcesHash = currentHash
        }
        loadGoals()
        updateGoals(using: goalRepository.goals)
    }

    func progress(for goal: Goal) -> Double {
        let currentTotal = totalValue(for: goal)
        guard goal.targetDecimal > 0 else { return 0 }
        let current = min(currentTotal, goal.targetDecimal)
        return NSDecimalNumber(decimal: current / goal.targetDecimal).doubleValue
    }

    func isAchieved(_ goal: Goal) -> Bool {
        Self.isAchieved(progress: progress(for: goal))
    }

    static func isAchieved(progress: Double) -> Bool {
        progress >= 0.999
    }

    static func urgencyLevel(
        targetDate: Date?,
        isBehind: Bool,
        isAchieved: Bool,
        referenceDate: Date = Date()
    ) -> GoalUrgencyLevel {
        guard let targetDate else { return .normal }
        guard !isAchieved else { return .normal }
        guard isBehind else { return .normal }

        let daysUntilTarget = referenceDate.startOfDay.daysBetween(targetDate.startOfDay)
        if daysUntilTarget < 0 {
            return .critical
        }
        return .warning
    }

    func totalValue(for goal: Goal) -> Decimal {
        if let accountId = goal.account?.safeId {
            return sourceRepository.sources
                .filter { $0.account?.id == accountId }
                .reduce(Decimal.zero) { $0 + $1.latestValue }
        }
        return totalValue
    }

    func paceStatus(for goal: Goal) -> GoalPaceStatus? {
        guard let targetDate = goal.targetDate else { return nil }
        let targetDay = targetDate.startOfDay
        let actualProgress = progress(for: goal)
        let startDate = goal.createdAt.startOfDay
        let totalDays = max(1, startDate.daysBetween(targetDay))

        if actualProgress >= 1 {
            return GoalPaceStatus(
                expectedProgress: 1,
                delta: 0,
                isBehind: false,
                statusText: "Goal reached"
            )
        }

        if let estimatedCompletionDate = estimateCompletionDate(for: goal) {
            let estimatedDay = estimatedCompletionDate.startOfDay
            let deltaDays = targetDay.daysBetween(estimatedDay)
            let deltaPercent = min(abs(Double(deltaDays)) / Double(totalDays) * 100, 999)
            let isBehind = estimatedDay > targetDay
            let statusText = abs(deltaPercent) < 1
                ? "On track"
                : isBehind
                    ? String(format: "Behind by %.1f%%", deltaPercent)
                    : String(format: "Ahead by %.1f%%", deltaPercent)

            return GoalPaceStatus(
                expectedProgress: actualProgress,
                delta: isBehind ? -deltaPercent / 100 : deltaPercent / 100,
                isBehind: isBehind,
                statusText: statusText
            )
        }

        let elapsedDays = max(0, startDate.daysBetween(Date()))
        let expectedProgress = min(Double(elapsedDays) / Double(totalDays), 1)
        let delta = actualProgress - expectedProgress
        let isOverdue = Date() > targetDay && actualProgress < 1
        let isBehind = isOverdue || delta < -0.03
        let deltaPercent = abs(delta) * 100
        let statusText = isOverdue
            ? "Behind schedule • target passed"
            : delta >= 0
                ? String(format: "Ahead by %.1f%%", deltaPercent)
                : String(format: "Behind by %.1f%%", deltaPercent)

        return GoalPaceStatus(
            expectedProgress: expectedProgress,
            delta: delta,
            isBehind: isBehind,
            statusText: statusText
        )
    }

    func deleteGoal(_ goal: Goal) {
        goalRepository.deleteGoal(goal)
    }

    func archiveGoal(_ goal: Goal) {
        goalRepository.updateGoal(goal, isActive: !goal.isActive)
    }

    func estimateCompletionDate(for goal: Goal) -> Date? {
        // Performance: Use cached completion date if available
        if let cached = cachedCompletionDates[goal.id] {
            return cached
        }

        let sources: [InvestmentSource]
        if let accountId = goal.account?.safeId {
            sources = sourceRepository.sources.filter { $0.account?.id == accountId }
        } else {
            sources = sourceRepository.sources
        }
        let sourceIds = sources.compactMap { $0.id }
        guard !sourceIds.isEmpty else {
            cachedCompletionDates[goal.id] = nil
            return nil
        }

        // Performance: Use cached evolution data if available
        let cacheKey = goal.account?.safeId ?? UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
        let evolutionData: [(date: Date, value: Decimal)]
        if let cached = cachedEvolutionData[cacheKey] {
            evolutionData = cached
        } else {
            let snapshots = snapshotRepository.fetchSnapshots(
                for: sourceIds,
                months: maxHistoryMonths
            )
            evolutionData = calculateEvolutionData(from: snapshots)
            cachedEvolutionData[cacheKey] = evolutionData
        }

        guard evolutionData.count >= 3,
              let first = evolutionData.suffix(6).first,
              let last = evolutionData.suffix(6).last else {
            cachedCompletionDates[goal.id] = nil
            return nil
        }

        let monthsBetween = max(1, first.date.monthsBetween(last.date))
        let delta = last.value - first.value
        guard delta > 0 else {
            cachedCompletionDates[goal.id] = nil
            return nil
        }

        let monthlyGain = delta / Decimal(monthsBetween)
        guard monthlyGain > 0 else {
            cachedCompletionDates[goal.id] = nil
            return nil
        }

        let currentValue = totalValue(for: goal)
        let remaining = goal.targetDecimal - currentValue
        guard remaining > 0 else {
            let result = Date()
            cachedCompletionDates[goal.id] = result
            return result
        }

        let months = NSDecimalNumber(decimal: remaining / monthlyGain).doubleValue
        let monthsRounded = Int(ceil(months))
        let result = Calendar.current.date(byAdding: .month, value: monthsRounded, to: last.date)
        cachedCompletionDates[goal.id] = result
        return result
    }

    private func calculateEvolutionData(from snapshots: [Snapshot]) -> [(date: Date, value: Decimal)] {
        let sortedSnapshots = snapshots.sorted { $0.date < $1.date }
        let uniqueDates = Array(Set(sortedSnapshots.map { Calendar.current.startOfDay(for: $0.date) }))
            .sorted()
        guard !uniqueDates.isEmpty else { return [] }

        var snapshotsBySource: [UUID: [(date: Date, value: Decimal)]] = [:]
        for snapshot in sortedSnapshots {
            guard let sourceId = snapshot.source?.id else { continue }
            snapshotsBySource[sourceId, default: []].append(
                (date: snapshot.date, value: snapshot.decimalValue)
            )
        }

        var indices: [UUID: Int] = [:]
        var evolution: [(date: Date, value: Decimal)] = []

        for (index, date) in uniqueDates.enumerated() {
            let nextDate = index + 1 < uniqueDates.count
                ? uniqueDates[index + 1]
                : Date.distantFuture
            var total: Decimal = 0

            for (sourceId, sourceSnapshots) in snapshotsBySource {
                var currentIndex = indices[sourceId] ?? 0
                var latest: (date: Date, value: Decimal)?

                while currentIndex < sourceSnapshots.count && sourceSnapshots[currentIndex].date < nextDate {
                    latest = sourceSnapshots[currentIndex]
                    currentIndex += 1
                }

                indices[sourceId] = currentIndex

                if let latest {
                    total += latest.value
                }
            }

            evolution.append((date: date, value: total))
        }

        return evolution
    }

    // MARK: - Private helpers

    private func loadGoals() {
        let selectedAccountId = selectedAccount?.safeId
        if showAllAccounts || selectedAccountId == nil {
            goalRepository.fetchGoals()
        } else if let account = selectedAccount {
            goalRepository.fetchGoals(for: account)
        }
    }

    private func updateGoals(using repositoryGoals: [Goal]) {
        let selectedAccountId = selectedAccount?.safeId
        if showAllAccounts || selectedAccountId == nil {
            goals = repositoryGoals
        } else {
            goals = repositoryGoals.filter { $0.account?.id == selectedAccountId }
        }
        updateTotalValue()
    }

    private func updateTotalValue() {
        let selectedAccountId = selectedAccount?.safeId
        if showAllAccounts || selectedAccountId == nil {
            totalValue = sourceRepository.sources.reduce(Decimal.zero) { $0 + $1.latestValue }
            return
        }

        totalValue = sourceRepository.sources
            .filter { $0.account?.id == selectedAccountId }
            .reduce(Decimal.zero) { $0 + $1.latestValue }
    }
}

struct GoalPaceStatus {
    let expectedProgress: Double
    let delta: Double
    let isBehind: Bool
    let statusText: String
}

enum GoalUrgencyLevel: Equatable {
    case normal
    case warning
    case critical
}
