import Foundation
import Combine
import CoreData

@MainActor
class MonthlyCheckInViewModel: ObservableObject {
    @Published var sourcesNeedingUpdate: [InvestmentSource] = []
    @Published var sources: [InvestmentSource] = []
    @Published var monthlySummary: MonthlySummary = .empty
    @Published var recentNotes: [Snapshot] = []
    @Published var isLoading = false
    @Published var selectedAccount: Account?
    @Published var showAllAccounts = true
    @Published var selectedRange: DateRange = .thisMonth
    @Published var checkInStats: MonthlyCheckInStats = .empty

    private let sourceRepository: InvestmentSourceRepository
    private let snapshotRepository: SnapshotRepository
    private let calculationService: CalculationService
    private var cancellables = Set<AnyCancellable>()

    @MainActor
    init(
        sourceRepository: InvestmentSourceRepository? = nil,
        snapshotRepository: SnapshotRepository? = nil,
        calculationService: CalculationService? = nil
    ) {
        let context = CoreDataStack.shared.viewContext
        self.sourceRepository = sourceRepository ?? InvestmentSourceRepository(context: context)
        self.snapshotRepository = snapshotRepository ?? SnapshotRepository(context: context)
        self.calculationService = calculationService ?? .shared

        setupObservers()
        refresh()
    }

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

        NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange)
            .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.refresh()
            }
            .store(in: &cancellables)
    }

    func refresh() {
        isLoading = true
        let filtered = filteredSources()
        let snapshots = filteredSnapshots(for: filtered)

        let accountFilter = showAllAccounts ? nil : selectedAccount
        sourcesNeedingUpdate = sourceRepository.fetchSourcesNeedingUpdate(for: accountFilter)
        sources = filtered

        monthlySummary = calculationService.calculateMonthlySummary(
            sources: filtered,
            snapshots: snapshots,
            range: selectedRange
        )

        checkInStats = MonthlyCheckInStore.stats(referenceDate: selectedRange.end)

        recentNotes = snapshots
            .filter { ($0.notes?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false)
                && selectedRange.contains($0.date) }
            .sorted { $0.date > $1.date }
            .prefix(5)
            .map { $0 }

        isLoading = false
    }

    func duplicatePreviousMonthSnapshots(referenceDate: Date) {
        let targetRange = DateRange.month(containing: referenceDate)
        let previousRange = DateRange.month(containing: referenceDate.adding(months: -1))
        let sources = filteredSources()
        guard !sources.isEmpty else { return }

        let targetSnapshots = snapshotRepository.fetchSnapshots(
            from: targetRange.start,
            to: targetRange.end
        )
        let targetSourceIds = Set(targetSnapshots.compactMap { $0.source?.id })

        let targetDate = Date()

        for source in sources {
            let sourceId = source.id
            if targetSourceIds.contains(sourceId) { continue }

            let previousSnapshots = snapshotRepository.fetchSnapshots(for: source)
            guard let previousSnapshot = previousSnapshots.first(where: { previousRange.contains($0.date) }) else {
                continue
            }

            snapshotRepository.createSnapshot(
                for: source,
                date: targetDate,
                value: previousSnapshot.decimalValue,
                contribution: previousSnapshot.contribution?.decimalValue,
                notes: previousSnapshot.notes
            )
        }
    }

    private func filteredSources() -> [InvestmentSource] {
        let selectedAccountId = selectedAccount?.safeId
        if showAllAccounts || selectedAccountId == nil {
            return sourceRepository.sources
        }
        return sourceRepository.sources.filter { $0.account?.id == selectedAccountId }
    }

    private func filteredSnapshots(for sources: [InvestmentSource]) -> [Snapshot] {
        let sourceIds = Set(sources.compactMap { $0.id })
        return snapshotRepository.fetchAllSnapshots().filter { snapshot in
            let sourceId = snapshot.source?.id
            return sourceId.map(sourceIds.contains) ?? false
        }
    }
}
