class CoinbaseItemsController < ApplicationController
  before_action :set_coinbase_item, only: [ :show, :edit, :update, :destroy, :sync, :setup_accounts, :complete_account_setup ]
  before_action :require_admin!, only: [ :new, :create, :preload_accounts, :select_accounts, :link_accounts, :select_existing_account, :link_existing_account, :edit, :update, :destroy, :sync, :setup_accounts, :complete_account_setup ]

  def index
    @coinbase_items = Current.family.coinbase_items.ordered
  end

  def show
  end

  def new
    @coinbase_item = Current.family.coinbase_items.build
  end

  def edit
  end

  def create
    @coinbase_item = Current.family.coinbase_items.build(coinbase_item_params)
    @coinbase_item.name ||= t(".default_name")

    if @coinbase_item.save
      # Set default institution metadata
      @coinbase_item.set_coinbase_institution_defaults!

      # Trigger initial sync to fetch accounts
      @coinbase_item.sync_later

      if turbo_frame_request?
        flash.now[:notice] = t(".success")
        @coinbase_items = Current.family.coinbase_items.ordered
        render turbo_stream: [
          turbo_stream.replace(
            "coinbase-providers-panel",
            partial: "settings/providers/coinbase_panel",
            locals: { coinbase_items: @coinbase_items }
          ),
          *flash_notification_stream_items
        ]
      else
        redirect_to settings_providers_path, notice: t(".success"), status: :see_other
      end
    else
      @error_message = @coinbase_item.errors.full_messages.join(", ")

      if turbo_frame_request?
        render turbo_stream: turbo_stream.replace(
          "coinbase-providers-panel",
          partial: "settings/providers/coinbase_panel",
          locals: { error_message: @error_message }
        ), status: :unprocessable_entity
      else
        redirect_to settings_providers_path, alert: @error_message, status: :unprocessable_entity
      end
    end
  end

  def update
    if @coinbase_item.update(coinbase_item_params)
      if turbo_frame_request?
        flash.now[:notice] = t(".success")
        @coinbase_items = Current.family.coinbase_items.ordered
        render turbo_stream: [
          turbo_stream.replace(
            "coinbase-providers-panel",
            partial: "settings/providers/coinbase_panel",
            locals: { coinbase_items: @coinbase_items }
          ),
          *flash_notification_stream_items
        ]
      else
        redirect_to settings_providers_path, notice: t(".success"), status: :see_other
      end
    else
      @error_message = @coinbase_item.errors.full_messages.join(", ")

      if turbo_frame_request?
        render turbo_stream: turbo_stream.replace(
          "coinbase-providers-panel",
          partial: "settings/providers/coinbase_panel",
          locals: { error_message: @error_message }
        ), status: :unprocessable_entity
      else
        redirect_to settings_providers_path, alert: @error_message, status: :unprocessable_entity
      end
    end
  end

  def destroy
    @coinbase_item.destroy_later
    redirect_to settings_providers_path, notice: t(".success")
  end

  def sync
    unless @coinbase_item.syncing?
      @coinbase_item.sync_later
    end

    respond_to do |format|
      format.html { redirect_back_or_to accounts_path }
      format.json { head :ok }
    end
  end

  # Legacy provider linking flow (not used - Coinbase uses setup_accounts flow instead)
  # These exist for route compatibility but redirect to the providers page.

  def preload_accounts
    redirect_to settings_providers_path
  end

  def select_accounts
    redirect_to settings_providers_path
  end

  def link_accounts
    redirect_to settings_providers_path
  end

  def select_existing_account
    @account = Current.family.accounts.find(params[:account_id])

    # List all available Coinbase accounts for the family that can be linked
    @available_coinbase_accounts = Current.family.coinbase_items
      .includes(coinbase_accounts: [ :account, { account_provider: :account } ])
      .flat_map(&:coinbase_accounts)
      # Show accounts that are still linkable:
      # - Already linked via AccountProvider (can be relinked to different account)
      # - Or fully unlinked (no account_provider)
      .select { |ca| ca.account.present? || ca.account_provider.nil? }
      .sort_by { |ca| ca.updated_at || ca.created_at }
      .reverse

    render :select_existing_account, layout: false
  end

  def link_existing_account
    @account = Current.family.accounts.find(params[:account_id])

    # Scope lookup to family's coinbase accounts for security
    coinbase_account = Current.family.coinbase_items
      .joins(:coinbase_accounts)
      .where(coinbase_accounts: { id: params[:coinbase_account_id] })
      .first&.coinbase_accounts&.find_by(id: params[:coinbase_account_id])

    unless coinbase_account
      flash[:alert] = t(".errors.invalid_coinbase_account")
      if turbo_frame_request?
        render turbo_stream: Array(flash_notification_stream_items)
      else
        redirect_to account_path(@account), alert: flash[:alert]
      end
      return
    end

    # Guard: only manual accounts can be linked (no existing provider links)
    if @account.account_providers.any? || @account.plaid_account_id.present? || @account.simplefin_account_id.present?
      flash[:alert] = t(".errors.only_manual")
      if turbo_frame_request?
        return render turbo_stream: Array(flash_notification_stream_items)
      else
        return redirect_to account_path(@account), alert: flash[:alert]
      end
    end

    # Relink behavior: detach any existing link and point provider link at the chosen account
    Account.transaction do
      coinbase_account.lock!

      # Upsert the AccountProvider mapping
      ap = AccountProvider.find_or_initialize_by(provider: coinbase_account)
      previous_account = ap.account
      ap.account_id = @account.id
      ap.save!

      # If the provider was previously linked to a different account in this family,
      # and that account is now orphaned, queue it for deletion
      if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id
        begin
          previous_account.reload
          if previous_account.account_providers.none?
            previous_account.destroy_later if previous_account.may_mark_for_deletion?
          end
        rescue => e
          Rails.logger.warn("Failed orphan cleanup for account ##{previous_account&.id}: #{e.class} - #{e.message}")
        end
      end
    end

    if turbo_frame_request?
      coinbase_account.reload
      item = coinbase_account.coinbase_item
      item.reload

      @manual_accounts = Account.uncached {
        Current.family.accounts
          .visible_manual
          .order(:name)
          .to_a
      }
      @coinbase_items = Current.family.coinbase_items.ordered.includes(:syncs)

      flash[:notice] = t(".success")
      @account.reload
      manual_accounts_stream = if @manual_accounts.any?
        turbo_stream.update(
          "manual-accounts",
          partial: "accounts/index/manual_accounts",
          locals: { accounts: @manual_accounts }
        )
      else
        turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
      end

      render turbo_stream: [
        turbo_stream.replace(
          ActionView::RecordIdentifier.dom_id(item),
          partial: "coinbase_items/coinbase_item",
          locals: { coinbase_item: item }
        ),
        manual_accounts_stream,
        *Array(flash_notification_stream_items)
      ]
    else
      redirect_to accounts_path, notice: t(".success")
    end
  end

  def setup_accounts
    # Only show unlinked accounts
    @coinbase_accounts = @coinbase_item.coinbase_accounts
      .left_joins(:account_provider)
      .where(account_providers: { id: nil })
      .order(:name)
  end

  def complete_account_setup
    selected_accounts = Array(params[:selected_accounts]).reject(&:blank?)

    created_accounts = []

    selected_accounts.each do |coinbase_account_id|
      # Find account - scoped to this item to prevent cross-item manipulation
      coinbase_account = @coinbase_item.coinbase_accounts.find_by(id: coinbase_account_id)
      unless coinbase_account
        Rails.logger.warn("Coinbase account #{coinbase_account_id} not found for item #{@coinbase_item.id}")
        next
      end

      # Lock row to prevent concurrent account creation (race condition protection)
      coinbase_account.with_lock do
        # Re-check after acquiring lock - another request may have created the account
        if coinbase_account.account.present?
          Rails.logger.info("Coinbase account #{coinbase_account_id} already linked, skipping")
          next
        end

        # Create account as Crypto exchange (all Coinbase accounts are crypto)
        account = Account.create_from_coinbase_account(coinbase_account)
        coinbase_account.ensure_account_provider!(account)
        created_accounts << account
      end

      # Reload to pick up the new account_provider association (outside lock)
      coinbase_account.reload

      # Process holdings immediately so user sees them right away
      # (sync_later is async and would delay holdings visibility)
      begin
        CoinbaseAccount::HoldingsProcessor.new(coinbase_account).process
      rescue => e
        Rails.logger.error("Failed to process holdings for #{coinbase_account.id}: #{e.message}")
      end
    end

    # Only clear pending if ALL accounts are now linked
    unlinked_remaining = @coinbase_item.coinbase_accounts
      .left_joins(:account_provider)
      .where(account_providers: { id: nil })
      .count
    @coinbase_item.update!(pending_account_setup: unlinked_remaining > 0)

    # Set appropriate flash message
    if created_accounts.any?
      flash[:notice] = t(".success", count: created_accounts.count)
    elsif selected_accounts.empty?
      flash[:notice] = t(".none_selected")
    else
      flash[:notice] = t(".no_accounts")
    end

    # Trigger a sync to process the newly linked accounts
    @coinbase_item.sync_later if created_accounts.any?

    if turbo_frame_request?
      @coinbase_items = Current.family.coinbase_items.ordered.includes(:syncs)

      render turbo_stream: [
        turbo_stream.replace(
          ActionView::RecordIdentifier.dom_id(@coinbase_item),
          partial: "coinbase_items/coinbase_item",
          locals: { coinbase_item: @coinbase_item }
        )
      ] + Array(flash_notification_stream_items)
    else
      redirect_to accounts_path, status: :see_other
    end
  end

  private

    def set_coinbase_item
      @coinbase_item = Current.family.coinbase_items.find(params[:id])
    end

    def coinbase_item_params
      params.require(:coinbase_item).permit(
        :name,
        :sync_start_date,
        :api_key,
        :api_secret
      )
    end
end
