import Foundation
import CoreData
import Combine

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

    @Published private(set) var goals: [Goal] = []

    private var cancellables = Set<AnyCancellable>()

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

    private func setupNotificationObserver() {
        NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: context)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.fetchGoals()
            }
            .store(in: &cancellables)

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

    // MARK: - Fetch

    func fetchGoals(for account: Account? = nil) {
        let request: NSFetchRequest<Goal> = Goal.fetchRequest()
        if let account = account {
            request.predicate = NSPredicate(format: "account == %@", account)
        }
        request.sortDescriptors = [
            NSSortDescriptor(keyPath: \Goal.createdAt, ascending: true)
        ]
        // Performance: Add batch size for fetches
        request.fetchBatchSize = 50

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

    // MARK: - Create

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

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

    @discardableResult
    func createGoal(
        name: String,
        targetAmount: Decimal,
        targetDate: Date? = nil,
        account: Account?
    ) -> Goal {
        // Check if goal with same name already exists in this account
        if let existing = findGoal(byName: name, in: account) {
            return existing
        }

        let goal = Goal(context: context)
        goal.name = name.trimmingCharacters(in: .whitespacesAndNewlines)
        goal.targetAmount = NSDecimalNumber(decimal: targetAmount)
        goal.targetDate = targetDate
        goal.account = account
        save()
        return goal
    }

    // MARK: - Update

    func updateGoal(
        _ goal: Goal,
        name: String? = nil,
        targetAmount: Decimal? = nil,
        targetDate: Date? = nil,
        clearTargetDate: Bool = false,
        isActive: Bool? = nil
    ) {
        if let name = name {
            goal.name = name
        }
        if let targetAmount = targetAmount {
            goal.targetAmount = NSDecimalNumber(decimal: targetAmount)
        }
        if clearTargetDate {
            goal.targetDate = nil
        } else if let targetDate = targetDate {
            goal.targetDate = targetDate
        }
        if let isActive = isActive {
            goal.isActive = isActive
        }
        save()
    }

    func deleteGoal(_ goal: Goal) {
        context.delete(goal)
        save()
    }

    // MARK: - Save

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