class Assistant::Function::CreateGoal < Assistant::Function
  class << self
    def name
      "create_goal"
    end

    def description
      <<~INSTRUCTIONS
        Creates a goal for the user's family.

        Use when the user describes a target they want to save toward — e.g.
        "vacation in 4 months for $5000", "downpayment for a car next year",
        "build an emergency fund of $10k".

        Before calling, confirm the key details by paraphrasing back to the
        user: the name, target amount, target date (if mentioned), and which
        of their accounts will fund it. Only call once they've confirmed.

        Constraints:
        - The goal must link to at least one of the user's Depository
          accounts (checking, savings, HSA, CD, money-market).
        - All linked accounts must share the same currency.
        - Use account names exactly as listed in the user's Depository
          accounts.

        On success returns the new goal's URL so you can point the user to
        it. On a soft failure (e.g. account name doesn't match), the
        response includes the available account list so you can re-ask.
      INSTRUCTIONS
    end
  end

  def strict_mode?
    false
  end

  def params_schema
    build_schema(
      required: %w[name target_amount linked_account_names],
      properties: {
        name: {
          type: "string",
          description: "Short goal name, e.g. 'Vacation in Italy'."
        },
        target_amount: {
          type: "number",
          description: "Total amount to save, in the linked accounts' currency."
        },
        target_date: {
          type: "string",
          description: "Optional ISO 8601 date (YYYY-MM-DD) for when the user wants to reach the target."
        },
        linked_account_names: {
          type: "array",
          items: { type: "string" },
          description: "Names of the user's Depository accounts to link. Must contain at least one. Use names exactly as they appear in the available accounts list."
        },
        initial_contribution: {
          type: "object",
          description: "Optional starting contribution at creation time.",
          properties: {
            amount: { type: "number" },
            source_account_name: { type: "string", description: "Must be one of the linked_account_names." }
          }
        },
        notes: {
          type: "string",
          description: "Optional freeform notes."
        }
      }
    )
  end

  def call(params = {})
    name = params["name"].to_s.strip
    target_amount = parse_decimal(params["target_amount"])
    target_date = parse_date(params["target_date"])
    linked_account_names = Array(params["linked_account_names"]).map { |n| n.to_s.strip }.reject(&:blank?)
    initial = params["initial_contribution"]
    notes = params["notes"].to_s.strip

    return error("name_required", "Please provide a name for the goal.") if name.blank?

    return error("target_amount_invalid", "Target amount must be greater than zero.") unless target_amount && target_amount > 0

    if linked_account_names.empty?
      return error(
        "no_linked_accounts",
        "Please specify at least one Depository account to link to this goal.",
        available_accounts: depository_account_payload
      )
    end

    matched = family.accounts.where(accountable_type: "Depository").visible.where(name: linked_account_names).to_a
    missing = linked_account_names - matched.map(&:name)
    if missing.any?
      return error(
        "unknown_accounts",
        "Some account names didn't match the user's Depository accounts.",
        unknown_names: missing,
        available_accounts: depository_account_payload
      )
    end

    currencies = matched.map(&:currency).uniq
    if currencies.size > 1
      return error(
        "currency_mismatch",
        "All linked accounts must share the same currency. Found: #{currencies.join(', ')}."
      )
    end

    goal = nil
    Goal.transaction do
      goal = family.goals.new(
        name: name,
        target_amount: target_amount,
        target_date: target_date,
        currency: currencies.first,
        notes: notes.presence,
        color: Goal::COLORS.sample
      )
      matched.each { |a| goal.goal_accounts.build(account: a) }
      goal.save!

      create_initial_contribution!(goal, matched, initial)
    end

    {
      success: true,
      goal_id: goal.id,
      name: goal.name,
      target_amount_formatted: goal.target_amount_money.format,
      currency: goal.currency,
      target_date: goal.target_date&.iso8601,
      url: Rails.application.routes.url_helpers.goal_path(goal),
      linked_account_names: matched.map(&:name),
      message: "Created goal '#{goal.name}' (target #{goal.target_amount_money.format}). View it at #{Rails.application.routes.url_helpers.goal_path(goal)}."
    }
  rescue ActiveRecord::RecordInvalid => e
    error("validation_failed", e.record.errors.full_messages.join("; "))
  end

  private
    def create_initial_contribution!(goal, matched_accounts, initial)
      return unless initial.is_a?(Hash)

      amount = parse_decimal(initial["amount"])
      return unless amount && amount > 0

      source = matched_accounts.find { |a| a.name == initial["source_account_name"].to_s }
      raise ActiveRecord::RecordInvalid.new(goal) unless source

      goal.goal_contributions.create!(
        account: source,
        amount: amount,
        currency: goal.currency,
        source: "initial",
        contributed_at: Date.current
      )
    end

    def parse_decimal(value)
      return nil if value.nil?
      BigDecimal(value.to_s)
    rescue ArgumentError, TypeError
      nil
    end

    def parse_date(value)
      return nil if value.blank?
      Date.iso8601(value.to_s)
    rescue Date::Error
      nil
    end

    def depository_account_payload
      family.accounts.where(accountable_type: "Depository").visible.pluck(:name, :currency).map { |n, c| { name: n, currency: c } }
    end

    def error(key, message, extras = {})
      { success: false, error: key, message: message }.merge(extras)
    end
end
