<?php
/**
 * Donation model.
 *
 * @package Donatico\Models
 */

declare(strict_types=1);

namespace Donatico\Models;

/**
 * Represents a single donation record. Amount is always stored
 * in the smallest currency unit (cents for USD/EUR/GBP, paise for INR, etc.).
 */
class Donation {

	const STATUS_PENDING   = 'pending';
	const STATUS_COMPLETED = 'completed';
	const STATUS_FAILED    = 'failed';
	const STATUS_REFUNDED  = 'refunded';

	const STATUSES = array(
		self::STATUS_PENDING,
		self::STATUS_COMPLETED,
		self::STATUS_FAILED,
		self::STATUS_REFUNDED,
	);

	const SUPPORTED_CURRENCIES = array( 'USD', 'EUR', 'GBP', 'AUD', 'CAD', 'INR', 'BDT' );

	private int $id                        = 0;
	private string $donorName              = '';
	private string $donorEmail             = '';
	private int $amount                    = 0;
	private string $currency               = 'USD';
	private ?string $message               = null;
	private bool $isAnonymous              = false;
	private ?string $stripeSessionId       = null;
	private ?string $stripePaymentIntentId = null;
	private string $status                 = self::STATUS_PENDING;
	private string $createdAt              = '';
	private ?int $campaignId               = null;
	private ?int $tierId                   = null;
	private ?int $subscriptionId           = null;
	private ?string $stripeInvoiceId       = null;
	private int $isRenewal                 = 0;

	public function __construct() {
		$this->createdAt = gmdate( 'Y-m-d H:i:s' );
	}

	// -------------------------------------------------------------------------
	// Getters
	// -------------------------------------------------------------------------

	/** @return int */
	public function getId(): int {
		return $this->id;
	}

	/** @return string */
	public function getDonorName(): string {
		return $this->donorName;
	}

	/** @return string */
	public function getDonorEmail(): string {
		return $this->donorEmail;
	}

	/** @return int Amount in smallest currency unit. */
	public function getAmount(): int {
		return $this->amount;
	}

	/** @return string Three-letter ISO currency code. */
	public function getCurrency(): string {
		return $this->currency;
	}

	/** @return string|null */
	public function getMessage(): ?string {
		return $this->message;
	}

	/** @return bool */
	public function isAnonymous(): bool {
		return $this->isAnonymous;
	}

	/** @return string|null */
	public function getStripeSessionId(): ?string {
		return $this->stripeSessionId;
	}

	/** @return string|null */
	public function getStripePaymentIntentId(): ?string {
		return $this->stripePaymentIntentId;
	}

	/** @return string One of the STATUS_* constants. */
	public function getStatus(): string {
		return $this->status;
	}

	/** @return string MySQL-formatted datetime. */
	public function getCreatedAt(): string {
		return $this->createdAt;
	}

	/** @return int|null */
	public function getCampaignId(): ?int {
		return $this->campaignId;
	}

	/** @return int|null */
	public function getTierId(): ?int {
		return $this->tierId;
	}

	/** @return int|null Local subscriptions table PK. */
	public function getSubscriptionId(): ?int {
		return $this->subscriptionId;
	}

	/** @return string|null */
	public function getStripeInvoiceId(): ?string {
		return $this->stripeInvoiceId;
	}

	/** @return int 1 for subscription renewal rows, 0 for one-time and first payments. */
	public function getIsRenewal(): int {
		return $this->isRenewal;
	}

	// -------------------------------------------------------------------------
	// Setters
	// -------------------------------------------------------------------------

	/** @return void */
	public function setId( int $id ): void {
		$this->id = $id;
	}

	/** @return void */
	public function setDonorName( string $donorName ): void {
		$this->donorName = $donorName;
	}

	/** @return void */
	public function setDonorEmail( string $donorEmail ): void {
		$this->donorEmail = $donorEmail;
	}

	/** @return void */
	public function setAmount( int $amount ): void {
		$this->amount = max( 0, $amount );
	}

	/** @return void */
	public function setCurrency( string $currency ): void {
		$upper = strtoupper( substr( $currency, 0, 3 ) );
		$this->currency = in_array( $upper, self::SUPPORTED_CURRENCIES, true ) ? $upper : 'USD';
	}

	/** @return void */
	public function setMessage( ?string $message ): void {
		$this->message = $message;
	}

	/** @return void */
	public function setIsAnonymous( bool $isAnonymous ): void {
		$this->isAnonymous = $isAnonymous;
	}

	/** @return void */
	public function setStripeSessionId( ?string $id ): void {
		$this->stripeSessionId = $id;
	}

	/** @return void */
	public function setStripePaymentIntentId( ?string $id ): void {
		$this->stripePaymentIntentId = $id;
	}

	/** @return void */
	public function setStatus( string $status ): void {
		$this->status = in_array( $status, self::STATUSES, true ) ? $status : self::STATUS_PENDING;
	}

	/** @return void */
	public function setCreatedAt( string $createdAt ): void {
		$this->createdAt = $createdAt;
	}

	/** @return void */
	public function setCampaignId( ?int $campaignId ): void {
		$this->campaignId = $campaignId;
	}

	/** @return void */
	public function setTierId( ?int $tierId ): void {
		$this->tierId = $tierId;
	}

	/** @return void */
	public function setSubscriptionId( ?int $subscriptionId ): void {
		$this->subscriptionId = $subscriptionId;
	}

	/** @return void */
	public function setStripeInvoiceId( ?string $stripeInvoiceId ): void {
		$this->stripeInvoiceId = $stripeInvoiceId;
	}

	/** @return void */
	public function setIsRenewal( int $isRenewal ): void {
		$this->isRenewal = $isRenewal !== 0 ? 1 : 0;
	}

	// -------------------------------------------------------------------------
	// Helpers
	// -------------------------------------------------------------------------

	/**
	 * Amount converted to the major currency unit (e.g. dollars from cents).
	 *
	 * @return float
	 */
	public function getAmountInMajorUnit(): float {
		return $this->amount / 100;
	}

	/**
	 * Human-readable amount string, e.g. "25.00".
	 *
	 * @return string
	 */
	public function getFormattedAmount(): string {
		return number_format( $this->getAmountInMajorUnit(), 2 );
	}

	/**
	 * Display name: shows "Anonymous" when the donor opted out.
	 *
	 * @return string
	 */
	public function getDisplayName(): string {
		return $this->isAnonymous ? __( 'Anonymous', 'donatico' ) : $this->donorName;
	}

	// -------------------------------------------------------------------------
	// Hydration
	// -------------------------------------------------------------------------

	/**
	 * Hydrate a Donation from a flat associative array (e.g. a $wpdb row).
	 *
	 * @param array<string, mixed> $data
	 * @return static
	 */
	public static function fromArray( array $data ): self {
		$d = new self();

		if ( isset( $data['id'] ) )              $d->setId( (int) $data['id'] );
		if ( isset( $data['donor_name'] ) )      $d->setDonorName( (string) $data['donor_name'] );
		if ( isset( $data['donor_email'] ) )     $d->setDonorEmail( (string) $data['donor_email'] );
		if ( isset( $data['amount'] ) )          $d->setAmount( (int) $data['amount'] );
		if ( isset( $data['currency'] ) )        $d->setCurrency( (string) $data['currency'] );
		if ( array_key_exists( 'message', $data ) )                    $d->setMessage( $data['message'] );
		if ( isset( $data['is_anonymous'] ) )    $d->setIsAnonymous( (bool) $data['is_anonymous'] );
		if ( array_key_exists( 'stripe_session_id', $data ) )         $d->setStripeSessionId( $data['stripe_session_id'] );
		if ( array_key_exists( 'stripe_payment_intent_id', $data ) )  $d->setStripePaymentIntentId( $data['stripe_payment_intent_id'] );
		if ( isset( $data['status'] ) )          $d->setStatus( (string) $data['status'] );
		if ( isset( $data['created_at'] ) )      $d->setCreatedAt( (string) $data['created_at'] );
		if ( array_key_exists( 'campaign_id', $data ) )     $d->setCampaignId( $data['campaign_id'] !== null ? (int) $data['campaign_id'] : null );
		if ( array_key_exists( 'tier_id', $data ) )         $d->setTierId( $data['tier_id'] !== null ? (int) $data['tier_id'] : null );
		if ( array_key_exists( 'subscription_id', $data ) ) $d->setSubscriptionId( $data['subscription_id'] !== null ? (int) $data['subscription_id'] : null );
		if ( array_key_exists( 'stripe_invoice_id', $data ) )         $d->setStripeInvoiceId( $data['stripe_invoice_id'] );
		if ( isset( $data['is_renewal'] ) )      $d->setIsRenewal( (int) $data['is_renewal'] );

		return $d;
	}

	/**
	 * Serialize to a flat array suitable for $wpdb insert/update.
	 * Does not include 'id' — callers add that as a WHERE clause.
	 *
	 * @return array<string, mixed>
	 */
	public function toArray(): array {
		return array(
			'donor_name'               => $this->donorName,
			'donor_email'              => $this->donorEmail,
			'amount'                   => $this->amount,
			'currency'                 => $this->currency,
			'message'                  => $this->message,
			'is_anonymous'             => (int) $this->isAnonymous,
			'stripe_session_id'        => $this->stripeSessionId,
			'stripe_payment_intent_id' => $this->stripePaymentIntentId,
			'status'                   => $this->status,
			'created_at'               => $this->createdAt,
			'campaign_id'              => $this->campaignId,
			'tier_id'                  => $this->tierId,
			'subscription_id'          => $this->subscriptionId,
			'stripe_invoice_id'        => $this->stripeInvoiceId,
			'is_renewal'               => $this->isRenewal,
		);
	}
}
