import Foundation
import Combine
import UIKit

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

    @Published var date = Date()
    @Published var valueString = ""
    @Published var contributionString = ""
    @Published var notes = ""
    @Published var includeContribution = false
    @Published var inputMode: InputMode = .simple
    @Published var currencySymbol = "€"
    private let currencyCode: String

    @Published var isValid = false
    @Published var errorMessage: String?
    @Published var clipboardValue: String?
    private var rawClipboardString: String?

    // MARK: - Mode

    enum Mode {
        case add
        case edit(Snapshot)
    }

    let mode: Mode
    let source: InvestmentSource

    /// Contribution stored on the snapshot when editing started (nil in add mode or
    /// when the snapshot had no contribution). Used to detect whether the user changed
    /// the contribution and should be offered to propagate it to other snapshots.
    private(set) var originalContribution: Decimal?

    // MARK: - Dependencies

    private var cancellables = Set<AnyCancellable>()

    // MARK: - Initialization

    init(source: InvestmentSource, mode: Mode = .add) {
        self.source = source
        self.mode = mode
        let settings = AppSettings.getOrCreate(in: CoreDataStack.shared.viewContext)
        if let accountCurrency = source.account?.currency, !accountCurrency.isEmpty {
            currencyCode = accountCurrency
            currencySymbol = CurrencyFormatter.symbol(for: accountCurrency)
        } else {
            currencyCode = settings.currency
            currencySymbol = settings.currencySymbol
        }
        if let accountMode = InputMode(rawValue: source.account?.inputMode ?? "") {
            inputMode = accountMode
        } else {
            inputMode = InputMode(rawValue: settings.inputMode) ?? .simple
        }
        includeContribution = inputMode == .detailed

        setupValidation()

        // Pre-fill for edit mode
        if case .edit(let snapshot) = mode {
            date = snapshot.date
            valueString = formatDecimalForInput(snapshot.decimalValue)
            if let contribution = snapshot.contribution {
                includeContribution = true
                contributionString = formatDecimalForInput(contribution.decimalValue)
                originalContribution = contribution.decimalValue
            }
            notes = snapshot.notes ?? ""
        }
    }

    // MARK: - Validation

    private func setupValidation() {
        Publishers.CombineLatest3($valueString, $contributionString, $includeContribution)
            .map { [weak self] valueStr, contribStr, includeContrib in
                self?.validateInputs(
                    valueString: valueStr,
                    contributionString: contribStr,
                    includeContribution: includeContrib
                ) ?? false
            }
            .assign(to: &$isValid)
    }

    private func validateInputs(
        valueString: String,
        contributionString: String,
        includeContribution: Bool
    ) -> Bool {
        // Value is required
        guard let value = parseDecimal(valueString), value >= 0 else {
            return false
        }

        // Contribution is optional but must be valid if included
        if includeContribution {
            let trimmed = contributionString.trimmingCharacters(in: .whitespacesAndNewlines)
            if !trimmed.isEmpty {
                guard let contribution = parseDecimal(trimmed), contribution >= 0 else {
                    return false
                }
            }
        }

        return true
    }

    // MARK: - Parsing

    private func parseDecimal(_ string: String) -> Decimal? {
        CurrencyFormatter.parseUserInput(string, currencySymbol: currencySymbol)
    }

    private func formatDecimalForInput(_ decimal: Decimal) -> String {
        CurrencyFormatter.formatForInput(decimal, currencyCode: currencyCode)
    }

    // MARK: - Computed Properties

    var value: Decimal? {
        parseDecimal(valueString)
    }

    var contribution: Decimal? {
        guard includeContribution else { return nil }
        let trimmed = contributionString.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return nil }
        return parseDecimal(trimmed)
    }

    /// True when the current contribution differs from the value the snapshot had when
    /// editing started. Drives the "propagate to other snapshots" prompt.
    var contributionChanged: Bool {
        contribution != originalContribution
    }

    var formattedValue: String {
        guard let value = value else { return CurrencyFormatter.format(Decimal.zero) }
        return value.currencyString
    }

    var title: String {
        switch mode {
        case .add:
            return "Add Snapshot"
        case .edit:
            return "Edit Snapshot"
        }
    }

    var buttonTitle: String {
        switch mode {
        case .add:
            return "Add Snapshot"
        case .edit:
            return "Save Changes"
        }
    }

    // MARK: - Previous Value Reference

    var previousValue: Decimal? {
        source.latestSnapshot?.decimalValue
    }

    var previousValueString: String {
        guard let previous = previousValue else { return "No previous value" }
        return "Previous: \(previous.currencyString)"
    }

    var changeFromPrevious: Decimal? {
        guard let current = value, let previous = previousValue else { return nil }
        return current - previous
    }

    var changePercentageFromPrevious: Double? {
        guard let current = value,
              let previous = previousValue,
              previous != 0 else { return nil }

        return NSDecimalNumber(decimal: (current - previous) / previous).doubleValue * 100
    }

    var formattedChange: String? {
        guard let change = changeFromPrevious else { return nil }
        let prefix = change >= 0 ? "+" : ""
        return "\(prefix)\(change.currencyString)"
    }

    var formattedChangePercentage: String? {
        guard let percentage = changePercentageFromPrevious else { return nil }
        let prefix = percentage >= 0 ? "+" : ""
        return String(format: "\(prefix)%.2f%%", percentage)
    }

    // MARK: - Duplicate Previous

    func prefillFromPreviousSnapshot() {
        guard case .add = mode,
              let previous = source.latestSnapshot else { return }

        valueString = formatDecimalForInput(previous.decimalValue)
        if let contribution = previous.contribution {
            includeContribution = true
            contributionString = formatDecimalForInput(contribution.decimalValue)
        }
        notes = previous.notes ?? ""
        date = Date()
    }

    // MARK: - Clipboard

    func checkClipboard() {
        guard let raw = UIPasteboard.general.string else {
            clipboardValue = nil
            rawClipboardString = nil
            return
        }

        guard let parsed = parseDecimal(raw), parsed > 0 else {
            clipboardValue = nil
            rawClipboardString = nil
            return
        }

        // Don't suggest if it matches what's already typed
        let formatted = formatDecimalForInput(parsed)
        if formatted == valueString {
            clipboardValue = nil
            rawClipboardString = nil
            return
        }

        rawClipboardString = raw
        clipboardValue = CurrencyFormatter.format(parsed, style: .currency, maximumFractionDigits: 2)
    }

    func applyClipboardValue() {
        guard let raw = rawClipboardString,
              let parsed = parseDecimal(raw) else { return }
        valueString = formatDecimalForInput(parsed)
        clipboardValue = nil
        rawClipboardString = nil
    }

    // MARK: - Date Validation

    var isDateInFuture: Bool {
        date > Date()
    }

    var dateWarning: String? {
        if isDateInFuture {
            return "Date is in the future"
        }
        return nil
    }
}
