<?php

namespace Civi\StripeImport;

use Civi\Api4\Contact;
use Civi\Api4\Contribution;
use Civi\Api4\ContributionRecur;
use Civi\Api4\Email;
use Civi\Api4\FinancialTrxn;
use Civi\Api4\StripeCustomer;
use CRM_StripeImport_ExtensionUtil as E;

class Charge extends Base {

  /**
   * @var string The Stripe charge ID
   */
  private string $stripeChargeID;

  public function setStripeChargeID(string $ChargeID) {
    $this->stripeChargeID = $ChargeID;
  }

  public function getStripeChargeID(): string {
    return $this->stripeChargeID;
  }

  /**
   * @var int The CiviCRM Contact ID
   */
  private int $contactID;

  public function setContactID(int $contactID) {
    $this->contactID = $contactID;
  }

  public function getContactID(): int {
    return $this->contactID;
  }

  public function hasContactID(): bool {
    return isset($this->contactID);
  }

  /**
   * @var int The CiviCRM Contribution ID
   */
  private int $contributionID;

  public function setContributionID(int $contributionID) {
    $this->contributionID = $contributionID;
  }

  public function getContributionID(): int {
    return $this->contributionID;
  }

  private string $description = '';

  public function setDescription(string $description) {
    $this->description = $description;
  }

  public function getDescription(): string {
    return $this->description;
  }

  private int $paymentInstrumentID;

  public function setPaymentInstrumentID(int $paymentInstrumentID) {
    $this->paymentInstrumentID = $paymentInstrumentID;
  }

  public function getPaymentInstrumentID(): int {
    return $this->paymentInstrumentID;
  }

  private int $financialTypeID;

  public function setFinancialTypeID(int $financialTypeID) {
    $this->financialTypeID = $financialTypeID;
  }

  public function getFinancialTypeID(): int {
    return $this->financialTypeID;
  }


  public function importOne(): array {
    // Retrieve the Stripe charge.
    $stripeCharge = $this->paymentProcessor->stripeClient->charges->retrieve($this->getStripeChargeID());

    // Get the related invoice.
    $stripeInvoiceID = \CRM_Stripe_Api::getObjectParam('invoice_id', $stripeCharge);
    if (!empty($stripeInvoiceID)) {
      $stripeInvoice = $this->paymentProcessor->stripeClient->invoices->retrieve($stripeCharge->invoice);
    }

    if (empty($this->getDescription())) {
      if (isset($stripeInvoice) && !empty(\CRM_Stripe_Api::getObjectParam('description', $stripeInvoice))) {
        $this->setDescription(\CRM_Stripe_Api::getObjectParam('description', $stripeInvoice) ?? '');
      }
    }
    if (empty($this->getDescription())) {
      $this->setDescription(\CRM_Stripe_Api::getObjectParam('description', $stripeCharge) ?? '');
    }
    if (empty($this->getDescription())) {
      $this->setDescription('Stripe: Manual import via API');
    }

    if (!$this->hasContactID()) {
      // Derive contact ID from stripe customer
      if (empty(\CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge))) {
        throw new \Exception('Missing customer_id in Stripe Charge. Cannot derive contact ID');
      }
      $stripeCustomer = \Civi\Api4\StripeCustomer::get(FALSE)
        ->addWhere('customer_id', '=', \CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge))
        ->addWhere('processor_id', '=', $this->getPaymentProcessorID())
        ->addClause('OR', ['currency', 'IS EMPTY'], ['currency', '=', \CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge)])
        ->addOrderBy('currency', 'DESC')
        ->execute()
        ->first();
      if (empty($stripeCustomer)) {
        throw new \Exception('Stripe Customer: ' . \CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge) . ' not found in CiviCRM. Cannot derive contact ID');
      }
      $this->setContactID($stripeCustomer['contact_id']);
    }

    // Check for a subscription.
    if (isset($stripeInvoice)) {
      $subscriptionID = \CRM_Stripe_Api::getObjectParam('subscription_id', $stripeInvoice);
      $contributionRecurID = NULL;
      if ($subscriptionID) {
        // Lookup the contribution_recur_id.
        $contributionRecur = ContributionRecur::get(FALSE)
          ->addWhere('trxn_id', '=', $subscriptionID)
          ->addWhere('is_test', '=', $this->paymentProcessor->getPaymentProcessor()['is_test'])
          ->execute()
          ->first();
        if (!$contributionRecur) {
          throw new \Exception(E::ts('The charge has a subscription, but the subscription is not in CiviCRM. Please import the subscription and try again.'));
        }
        $contributionRecurID = $contributionRecur['id'];
      }
    }

    // Prepare to either create or update a contribution record in CiviCRM.
    $contributionParams = [];

    // We update these parameters regardless if it's a new contribution
    // or an existing contributions.
    if (isset($stripeInvoice)) {
      $contributionParams['receive_date'] = \CRM_Stripe_Api::getObjectParam('receive_date', $stripeInvoice);
      $contributionParams['total_amount'] = \CRM_Stripe_Api::getObjectParam('total_amount', $stripeInvoice);
    }
    else {
      $contributionParams['receive_date'] = \CRM_Stripe_Api::getObjectParam('receive_date', $stripeCharge);
      $contributionParams['total_amount'] = \CRM_Stripe_Api::getObjectParam('total_amount', $stripeCharge);
    }

    // Check if a contribution already exists.
    if (empty($this->getContributionID())) {
      // See if we have a matching payment for this trxn_id and get the linked contribution ID
      $financialTrxn = FinancialTrxn::get(FALSE)
        ->addSelect('contribution.id')
        ->addJoin('Contribution AS contribution', 'LEFT', 'EntityFinancialTrxn')
        ->addWhere('is_payment', '=', TRUE)
        ->addWhere('trxn_id', '=', $this->getStripeChargeID())
        ->execute()
        ->first();
      if (!empty($financialTrxn['contribution.id'])) {
        $this->setContributionID($financialTrxn['contribution.id']);
      }
    }

    // If no contribution was found we need to create it
    if (empty($this->getContributionID())) {
      // We have to build all the parameters.
      $contributionParams['contact_id'] = $this->getContactID();
      $contributionParams['currency'] = \CRM_Stripe_Api::getObjectParam('currency', $stripeCharge);
      $contributionParams['trxn_id'] = $this->getStripeChargeID();
      $contributionParams['payment_instrument_id'] = $this->getPaymentInstrumentID();
      $contributionParams['financial_type_id'] = $this->getFinancialTypeID();
      $contributionParams['is_test'] = $this->paymentProcessor->getPaymentProcessor()['is_test'];
      $contributionParams['contribution_source'] = $this->getDescription();
      $contributionParams['contribution_status_id:name'] = 'Pending';
      if (!empty($contributionRecurID)) {
        $contributionParams['contribution_recur_id'] = $contributionRecurID;
      }
      $contribution = Contribution::create(FALSE)
        ->setValues($contributionParams)
        ->execute()
        ->first();
      $this->setContributionID($contribution['id']);
    }

    $webhookEventProcessor = new \Civi\Stripe\Webhook\Events($this->getPaymentProcessorID());
    $webhookEventProcessor->setData(\Stripe\StripeObject::constructFrom(['object' => $stripeCharge]));
    $return = $webhookEventProcessor->getResultObject();
    switch (\CRM_Stripe_Api::getObjectParam('status', $stripeCharge)) {
      case 'succeeded':
        $webhookEventProcessor->setEventType('charge.succeeded');
        $return = $webhookEventProcessor->doChargeSucceeded();
        break;

      case 'failed':
        $webhookEventProcessor->setEventType('charge.failed');
        $return = $webhookEventProcessor->doChargeFailed();
        break;

      case 'pending':
        // Nothing further to do (we already created the contribution and should not create a payment or failure yet)
        $return->ok = TRUE;
        $return->message = 'charge is pending';
    }

    return [
      'contribution_id' => $this->getContributionID(),
      'result' => $return,
    ];
  }

}