<?php

namespace App\Services;

use App\Models\TaxRefund;
use App\Models\RefundHistory;
use App\Notifications\RefundStatusNotification;
use Illuminate\Support\Facades\DB;
use Exception;

class AdminTaxRefundService
{
      /**
       * Allowed status transitions (state machine)
       */
      protected array $transitions = [
            'pending' => ['in_review', 'approved', 'rejected', 'on_hold'],
            'in_review' => ['approved', 'rejected', 'on_hold'],
            'approved' => ['processed', 'failed'],
            'on_hold' => ['in_review', 'rejected'],
            'rejected' => [],
            'processed' => [],
            'failed' => [],
      ];

      /**
       * Core status transition engine (single source of truth)
       */
      public function updateStatus(TaxRefund $refund, string $newStatus, ?string $note = null): TaxRefund
      {
            return DB::transaction(function () use ($refund, $newStatus, $note) {

                  // Lock row to prevent race conditions
                  $refund = TaxRefund::where('id', $refund->id)
                        ->lockForUpdate()
                        ->firstOrFail();

                  $this->validateStatus($newStatus);
                  $this->validateTransition($refund->status, $newStatus);
                  $this->validateBusinessRules($refund, $newStatus);

                  $refund->update([
                        'status' => $newStatus,
                  ]);

                  $this->logRefundAction($refund, $newStatus, $note);

                  // Notify AFTER successful commit
                  DB::afterCommit(function () use ($refund, $newStatus, $note) {
                        $this->notifyUser($refund, $newStatus, $note);
                  });

                  return $refund;
            });
      }

      /**
       * Approve wrapper
       */
      public function approveRefund(TaxRefund $refund, ?string $note = null): TaxRefund
      {
            return $this->updateStatus($refund, 'approved', $note);
      }

      /**
       * Reject wrapper
       */
      public function rejectRefund(TaxRefund $refund, string $reason): TaxRefund
      {
            return $this->updateStatus($refund, 'rejected', $reason);
      }

      /**
       * Hold wrapper
       */
      public function holdRefund(TaxRefund $refund, ?string $reason = null): TaxRefund
      {
            return $this->updateStatus($refund, 'on_hold', $reason);
      }

      /**
       * Bulk handler (no nested transactions)
       */
      protected function bulkUpdate(array $refundIds, string $status, ?string $note = null): int
      {
            return DB::transaction(function () use ($refundIds, $status, $note) {
                  /** @var \Illuminate\Database\Eloquent\Collection<int, \App\Models\TaxRefund> $refunds */
                  $refunds = TaxRefund::whereIn('id', $refundIds)
                        ->lockForUpdate()
                        ->get();

                  $count = 0;

                  foreach ($refunds as $refund) {
                        $this->validateStatus($status);
                        $this->validateTransition($refund->status, $status);
                        $this->validateBusinessRules($refund, $status);

                        $refund->update(['status' => $status]);
                        $this->logRefundAction($refund, $status, $note);

                        $count++;
                  }

                  DB::afterCommit(function () use ($refunds, $status, $note) {
                        foreach ($refunds as $refund) {
                              $this->notifyUser($refund, $status, $note);
                        }
                  });

                  return $count;
            });
      }

      public function bulkApprove(array $ids, ?string $note = null): int
      {
            return $this->bulkUpdate($ids, 'approved', $note);
      }

      public function bulkReject(array $ids, string $reason): int
      {
            return $this->bulkUpdate($ids, 'rejected', $reason);
      }

      public function bulkHold(array $ids, ?string $reason = null): int
      {
            return $this->bulkUpdate($ids, 'on_hold', $reason);
      }

      /**
       * Delete refund (no status transition needed)
       */
      public function deleteRefund(TaxRefund $refund, ?string $reason = 'Deleted by admin'): void
      {
            DB::transaction(function () use ($refund, $reason) {

                  $refund = TaxRefund::where('id', $refund->id)
                        ->lockForUpdate()
                        ->firstOrFail();

                  $this->logRefundAction($refund, 'deleted', $reason);

                  $refund->delete();
            });
      }

      /**
       * Validate allowed status
       */
      protected function validateStatus(string $status): void
      {
            $allowed = array_keys($this->transitions);

            if (!in_array($status, $allowed, true)) {
                  throw new Exception("Invalid status: {$status}");
            }
      }

      /**
       * Validate transition rules
       */
      protected function validateTransition(string $current, string $new): void
      {
            $allowed = $this->transitions[$current] ?? [];

            if (!in_array($new, $allowed, true)) {
                  throw new Exception("Invalid transition from {$current} to {$new}");
            }
      }

      /**
       * Business rules per transition
       */
      protected function validateBusinessRules(TaxRefund $refund, string $newStatus): void
      {
            if ($newStatus === 'approved') {
                  if (empty($refund->amount) || bccomp((string) $refund->amount, '0', 2) === 0) {
                        throw new Exception("Cannot approve refund without setting an amount.");
                  }
            }
      }

      /**
       * Log history
       */
      protected function logRefundAction(TaxRefund $refund, string $status, ?string $notes = null): void
      {
            RefundHistory::create([
                  'refund_id' => $refund->id,
                  'status' => $status,
                  'notes' => $notes,
                  'changed_at' => now(),
            ]);
      }
      /**
       * Bulk delete refunds
       */
      public function bulkDelete(array $refundIds, ?string $reason = 'Deleted by admin'): int
      {
            return DB::transaction(function () use ($refundIds, $reason) {

                  /** @var \Illuminate\Database\Eloquent\Collection<int, \App\Models\TaxRefund> $refunds */
                  $refunds = TaxRefund::whereIn('id', $refundIds)
                        ->lockForUpdate()
                        ->get();

                  $count = 0;

                  foreach ($refunds as $refund) {

                        // Log deletion before removing record
                        $this->logRefundAction($refund, 'deleted', $reason);

                        $refund->delete();
                        $count++;
                  }

                  return $count;
            });
      }

      public function setAmount(TaxRefund $refund, string $amount, bool $force = false): TaxRefund
      {
            return DB::transaction(function () use ($refund, $amount, $force) {

                  $refund = TaxRefund::where('id', $refund->id)
                        ->lockForUpdate()
                        ->firstOrFail();

                  if ($refund->status !== 'pending') {
                        throw new Exception("Amount can only be set for pending refunds.");
                  }

                  if (!is_null($refund->amount) && !$force) {
                        throw new Exception("Refund already has an amount. Confirm overwrite?");
                  }

                  if (bccomp($amount, '0', 2) <= 0) {
                        throw new Exception("Amount must be greater than zero.");
                  }

                  $refund->update([
                        'amount' => $amount,
                  ]);

                  return $refund;
            });
      }

      /**
       * Notify user
       */
      protected function notifyUser(TaxRefund $refund, string $status, ?string $note = null): void
      {
            $refund->user->notify(
                  new RefundStatusNotification($refund, $status, $note)
            );
      }
}
