class SnaptradeAccount < ApplicationRecord
  include CurrencyNormalizable, Encryptable
  include SnaptradeAccount::DataHelpers

  # Encrypt raw payloads if ActiveRecord encryption is configured
  if encryption_ready?
    encrypts :raw_payload
    encrypts :raw_transactions_payload
    encrypts :raw_holdings_payload
    encrypts :raw_activities_payload
  end

  belongs_to :snaptrade_item

  # Association through account_providers for linking to Sure accounts
  has_one :account_provider, as: :provider, dependent: :destroy
  has_one :linked_account, through: :account_provider, source: :account

  validates :name, :currency, presence: true
  validates :snaptrade_account_id, uniqueness: { scope: :snaptrade_item_id, allow_nil: true }

  # Enqueue cleanup job after destruction to avoid blocking transaction with API call
  after_destroy :enqueue_connection_cleanup

  # Helper to get the linked Sure account
  def current_account
    linked_account
  end

  # Ensure there is an AccountProvider link for this SnapTrade account and the given Account.
  # Safe and idempotent; returns the AccountProvider or nil if no account is provided.
  def ensure_account_provider!(account = nil)
    # If account_provider already exists, update it if needed
    if account_provider.present?
      account_provider.update!(account: account) if account && account_provider.account_id != account.id
      return account_provider
    end

    # Need an account to create the provider
    acct = account || current_account
    return nil unless acct

    provider = AccountProvider
      .find_or_initialize_by(provider_type: "SnaptradeAccount", provider_id: id)
      .tap do |p|
        p.account = acct
        p.save!
      end

    # Reload the association so future accesses don't return stale/nil value
    reload_account_provider

    provider
  rescue => e
    Rails.logger.warn("SnaptradeAccount##{id}: failed to ensure AccountProvider link: #{e.class} - #{e.message}")
    nil
  end

  # Import account data from SnapTrade API response
  # Expected JSON structure:
  # {
  #   "id": "uuid",
  #   "brokerage_authorization": "uuid",  # just a string, not an object
  #   "name": "Robinhood Individual",
  #   "number": "123456",
  #   "institution_name": "Robinhood",
  #   "balance": { "total": { "amount": 1000.00, "currency": "USD" } },
  #   "meta": { "type": "INDIVIDUAL", "institution_name": "Robinhood" }
  # }
  def upsert_from_snaptrade!(account_data)
    # Deep convert SDK objects to hashes - .to_h only does top level,
    # so we use JSON round-trip to get nested objects as hashes too
    data = sdk_object_to_hash(account_data)
    data = data.with_indifferent_access

    # Extract meta data
    meta_data = (data[:meta] || {}).with_indifferent_access

    # Extract balance data - currency is nested in balance.total
    balance_data = (data[:balance] || {}).with_indifferent_access
    total_balance = (balance_data[:total] || {}).with_indifferent_access

    # Institution name can be at top level or in meta
    institution_name = data[:institution_name] || meta_data[:institution_name]

    # brokerage_authorization is just a string ID, not an object
    auth_id = data[:brokerage_authorization]
    auth_id = auth_id[:id] if auth_id.is_a?(Hash) # handle both formats

    update!(
      snaptrade_account_id: data[:id],
      snaptrade_authorization_id: auth_id,
      account_number: data[:number],
      name: data[:name] || "#{institution_name} Account",
      brokerage_name: institution_name,
      currency: extract_currency_code(total_balance[:currency]) || "USD",
      account_type: meta_data[:type] || data[:raw_type],
      account_status: data[:status],
      current_balance: total_balance[:amount],
      institution_metadata: {
        name: institution_name,
        sync_status: data[:sync_status],
        portfolio_group: data[:portfolio_group]
      }.compact,
      raw_payload: data
    )
  end

  # Store holdings data from SnapTrade API
  def upsert_holdings_snapshot!(holdings_data)
    update!(
      raw_holdings_payload: holdings_data,
      last_holdings_sync: Time.current
    )
  end

  # Store activities data from SnapTrade API
  def upsert_activities_snapshot!(activities_data)
    update!(
      raw_activities_payload: activities_data,
      last_activities_sync: Time.current
    )
  end

  # Store balances data
  # NOTE: This only updates cash_balance, NOT current_balance.
  # current_balance represents total account value (holdings + cash)
  # and is set by upsert_from_snaptrade! from the balance.total field.
  def upsert_balances!(balances_data)
    # Deep convert each balance entry to ensure we have hashes
    data = Array(balances_data).map { |b| sdk_object_to_hash(b).with_indifferent_access }

    Rails.logger.info "SnaptradeAccount##{id} upsert_balances! - raw data: #{data.inspect}"

    # Find cash balance (usually in USD or account currency)
    cash_entry = data.find { |b| b.dig(:currency, :code) == currency } ||
                 data.find { |b| b.dig(:currency, :code) == "USD" } ||
                 data.first

    if cash_entry
      cash_value = cash_entry[:cash]
      Rails.logger.info "SnaptradeAccount##{id} upsert_balances! - setting cash_balance=#{cash_value}"

      # Only update cash_balance, preserve current_balance (total account value)
      update!(cash_balance: cash_value)
    end
  end

  # Get the SnapTrade provider instance via the parent item
  def snaptrade_provider
    snaptrade_item.snaptrade_provider
  end

  # Get SnapTrade credentials for API calls
  def snaptrade_credentials
    snaptrade_item.snaptrade_credentials
  end

  private

    # Enqueue a background job to clean up the SnapTrade connection
    # This runs asynchronously after the record is destroyed to avoid
    # blocking the DB transaction with an external API call
    def enqueue_connection_cleanup
      return unless snaptrade_authorization_id.present?

      SnaptradeConnectionCleanupJob.perform_later(
        snaptrade_item_id: snaptrade_item_id,
        authorization_id: snaptrade_authorization_id,
        account_id: id
      )
    end

    def log_invalid_currency(currency_value)
      Rails.logger.warn("Invalid currency code '#{currency_value}' for SnapTrade account #{id}, defaulting to USD")
    end

    # Extract currency code from either a string or a currency object (hash with :code key)
    # SnapTrade API may return currency as either format depending on the endpoint
    def extract_currency_code(currency_value)
      return nil if currency_value.blank?

      if currency_value.is_a?(Hash)
        # Currency object: { code: "USD", id: "..." }
        currency_value[:code] || currency_value["code"]
      else
        # String: "USD"
        parse_currency(currency_value)
      end
    end
end
