import Foundation
import CoreData
import Combine

@MainActor
class InvestmentSourceRepository: ObservableObject {
    private let context: NSManagedObjectContext

    @Published private(set) var sources: [InvestmentSource] = []

    private var cancellables = Set<AnyCancellable>()

    init(context: NSManagedObjectContext = CoreDataStack.shared.viewContext) {
        self.context = context
        fetchSources()
        setupNotificationObserver()
    }

    private func setupNotificationObserver() {
        NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: context)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] notification in
                guard let self, self.isRelevantChange(notification) else { return }
                self.fetchSources()
            }
            .store(in: &cancellables)

        NotificationCenter.default.publisher(for: .cloudKitForceReload)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.fetchSources() }
            .store(in: &cancellables)
    }

    // MARK: - Fetch

    func fetchSources(account: Account? = nil) {
        let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
        if let account = account {
            request.predicate = NSPredicate(format: "account == %@", account)
        }
        request.sortDescriptors = [
            NSSortDescriptor(keyPath: \InvestmentSource.name, ascending: true)
        ]
        // Performance: Add batch size for large datasets
        request.fetchBatchSize = 100

        do {
            sources = try context.fetch(request)
        } catch {
            print("Failed to fetch sources: \(error)")
            sources = []
        }
    }

    func fetchSource(by id: UUID) -> InvestmentSource? {
        let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
        request.predicate = NSPredicate(format: "id == %@", id as CVarArg)
        request.fetchLimit = 1
        return try? context.fetch(request).first
    }

    func fetchSources(for category: Category) -> [InvestmentSource] {
        let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
        request.predicate = NSPredicate(format: "category == %@", category)
        request.sortDescriptors = [
            NSSortDescriptor(keyPath: \InvestmentSource.name, ascending: true)
        ]

        return (try? context.fetch(request)) ?? []
    }

    func fetchActiveSources() -> [InvestmentSource] {
        sources.filter { $0.isActive }
    }

    func fetchSourcesNeedingUpdate(for account: Account? = nil) -> [InvestmentSource] {
        let filtered = account == nil ? sources : sources.filter { $0.account?.id == account?.id }
        return filtered.filter { $0.needsUpdate }
    }

    // MARK: - Create

    /// Check if a source with the given name already exists in the account (case-insensitive)
    func sourceExists(name: String, in account: Account?) -> Bool {
        let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
        let normalized = trimmed.lowercased()
        return sources.contains { source in
            let nameMatches = source.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == normalized
            let accountMatches = source.account?.id == account?.id
            return nameMatches && accountMatches
        }
    }

    /// Find an existing source by name (case-insensitive) within the same account
    func findSource(byName name: String, in account: Account?) -> InvestmentSource? {
        let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
        let normalized = trimmed.lowercased()
        return sources.first { source in
            let nameMatches = source.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == normalized
            let accountMatches = source.account?.id == account?.id
            return nameMatches && accountMatches
        }
    }

    @discardableResult
    func createSource(
        name: String,
        category: Category,
        notificationFrequency: NotificationFrequency = .monthly,
        customFrequencyMonths: Int = 1,
        account: Account? = nil
    ) -> InvestmentSource {
        // Check if source with same name already exists in this account
        if let existing = findSource(byName: name, in: account) {
            return existing
        }

        let source = InvestmentSource(context: context)
        source.name = name.trimmingCharacters(in: .whitespacesAndNewlines)
        source.category = category
        source.notificationFrequency = notificationFrequency.rawValue
        source.customFrequencyMonths = Int16(customFrequencyMonths)
        source.account = account

        save()
        return source
    }

    // MARK: - Update

    func updateSource(
        _ source: InvestmentSource,
        name: String? = nil,
        category: Category? = nil,
        notificationFrequency: NotificationFrequency? = nil,
        customFrequencyMonths: Int? = nil,
        isActive: Bool? = nil,
        account: Account? = nil
    ) {
        if let name = name {
            source.name = name
        }
        if let category = category {
            source.category = category
        }
        if let frequency = notificationFrequency {
            source.notificationFrequency = frequency.rawValue
        }
        if let customMonths = customFrequencyMonths {
            source.customFrequencyMonths = Int16(customMonths)
        }
        if let isActive = isActive {
            source.isActive = isActive
        }
        if let account = account {
            source.account = account
        }

        save()
    }

    func toggleActive(_ source: InvestmentSource) {
        source.isActive.toggle()
        save()
    }

    // MARK: - Delete

    func deleteSource(_ source: InvestmentSource) {
        if source.managedObjectContext == context {
            context.delete(source)
        } else {
            let objectInContext = context.object(with: source.objectID)
            context.delete(objectInContext)
        }
        save()
    }

    func deleteSource(at offsets: IndexSet) {
        for index in offsets {
            guard index < sources.count else { continue }
            context.delete(sources[index])
        }
        save()
    }

    // MARK: - Statistics

    var sourceCount: Int {
        sources.count
    }

    var activeSourceCount: Int {
        sources.filter { $0.isActive }.count
    }

    var totalValue: Decimal {
        sources.reduce(Decimal.zero) { $0 + $1.latestValue }
    }

    func topSources(limit: Int = 5) -> [InvestmentSource] {
        sources
            .sorted { $0.latestValue > $1.latestValue }
            .prefix(limit)
            .map { $0 }
    }

    // MARK: - Freemium Check

    var canAddMoreSources: Bool {
        // This will be checked against FreemiumValidator
        true
    }

    // MARK: - Save

    private func save() {
        guard context.hasChanges else { return }
        do {
            try context.save()
            // Note: Removed redundant fetchSources() call - the NotificationCenter observer
            // in setupNotificationObserver() already handles refetching on context changes
            CoreDataStack.shared.refreshWidgetData()
        } catch {
            print("Failed to save context: \(error)")
        }
    }

    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 InvestmentSource }) {
                    return true
                }
            }
        }
        return false
    }
}
