<?php

namespace App\Console\Commands;

use App\Models\Invoice;
use App\Models\Subscription;
use App\Services\Billing\BillingManager;
use Illuminate\Console\Command;
use Razorpay\Api\Api;
use Throwable;

/**
 * Backfills `invoices.hosted_url` for Razorpay rows that landed before the
 * webhook handler started fetching the invoice short_url. Walks every
 * Razorpay subscription with at least one missing-URL invoice, calls
 * GET /v1/invoices?subscription_id=:id, then matches by payment_id and
 * writes short_url onto the local row.
 */
class BackfillRazorpayInvoiceUrlsCommand extends Command
{
    protected $signature = 'razorpay:backfill-invoice-urls {--dry-run : Report without writing}';

    protected $description = 'Backfill hosted_url on Razorpay invoices missing it.';

    public function handle(BillingManager $manager): int
    {
        $provider = $manager->provider('razorpay');
        if (! $provider) {
            $this->error('Razorpay provider not configured.');

            return self::FAILURE;
        }

        $api = $this->resolveApi($provider);
        if (! $api) {
            return self::FAILURE;
        }

        $subIds = Invoice::query()
            ->where('provider_key', 'razorpay')
            ->whereNull('hosted_url')
            ->pluck('subscription_id')
            ->unique()
            ->values();

        if ($subIds->isEmpty()) {
            $this->info('Nothing to backfill — all Razorpay invoices have hosted_url.');

            return self::SUCCESS;
        }

        $dryRun = (bool) $this->option('dry-run');
        $updated = 0;
        $skipped = 0;

        foreach ($subIds as $localSubId) {
            $sub = Subscription::find($localSubId);
            if (! $sub || ! $sub->provider_subscription_id) {
                $skipped++;
                continue;
            }

            try {
                $response = $api->invoice->all(['subscription_id' => $sub->provider_subscription_id]);
            } catch (Throwable $e) {
                $this->warn("sub {$sub->provider_subscription_id}: {$e->getMessage()}");
                $skipped++;
                continue;
            }

            $byPayment = [];
            foreach (($response->items ?? []) as $rzpInvoice) {
                $paymentId = $rzpInvoice->payment_id ?? null;
                $shortUrl = $rzpInvoice->short_url ?? null;
                if ($paymentId && $shortUrl) {
                    $byPayment[$paymentId] = $shortUrl;
                }
            }

            $rows = Invoice::query()
                ->where('provider_key', 'razorpay')
                ->where('subscription_id', $sub->id)
                ->whereNull('hosted_url')
                ->get();

            foreach ($rows as $row) {
                $url = $byPayment[$row->provider_invoice_id] ?? null;
                if (! $url) {
                    continue;
                }

                if ($dryRun) {
                    $this->line("[dry-run] invoice #{$row->id} → {$url}");
                } else {
                    $row->update(['hosted_url' => $url]);
                }
                $updated++;
            }
        }

        $verb = $dryRun ? 'would update' : 'updated';
        $this->info("{$verb} {$updated} invoice(s); skipped {$skipped} subscription(s).");

        return self::SUCCESS;
    }

    /**
     * The Razorpay provider doesn't expose its `Api` instance publicly —
     * we reach through reflection so this command stays a one-off backfill
     * tool without expanding the BillingProvider contract surface.
     */
    protected function resolveApi(object $provider): ?Api
    {
        try {
            $method = new \ReflectionMethod($provider, 'api');
            $method->setAccessible(true);

            return $method->invoke($provider);
        } catch (Throwable $e) {
            $this->error('Could not resolve Razorpay API client: '.$e->getMessage());

            return null;
        }
    }
}
