<?php

namespace Licenzo\Services;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

use Licenzo\Casts\MoneyCast;
use Licenzo\Helper\Action;
use Licenzo\Helper\LicenzoKey;
use Licenzo\Model\Subscription;
use Licenzo\Model\SubscriptionProduct;
use Licenzo\Model\SubscriptionTransaction;
use Licenzo\Model\SubscriptionEvent;
use Licenzo\Model\Product;
use Licenzo\Model\ProductVariation;
use Licenzo\Model\License;
use Licenzo\Model\LicenseProduct;
use Licenzo\Model\Invoice;
use Licenzo\Model\InvoiceItem;
use Licenzo\Services\Integrations\ProviderAliasService;
use Licenzo\Framework\Support\Facades\DB;

class SubscriptionService
{
    /**
     * Create a new subscription
     */
    public static function create(array $data): array
    {
        if (empty($data['customer_id']) || empty($data['status']) || empty($data['products']) || !is_array($data['products'])) {
            return ['success' => false, 'message' => 'Invalid subscription data provided'];
        }

        try {
            DB::beginTransaction();

            $generatedLicenseKey = sanitize_text_field((string) ($data['generated_license_key'] ?? ''));

            $subscription = Subscription::create([
                'uuid'               => wp_generate_uuid4(),
                'customer_id'        => $data['customer_id'],
                'provider'           => !empty($data['provider'])
                    ? ProviderAliasService::normalize((string) sanitize_text_field((string) $data['provider']), SubscriptionTransaction::PROVIDER_MANUAL)
                    : SubscriptionTransaction::PROVIDER_MANUAL,
                'provider_id'        => !empty($data['provider_id']) ? (string) $data['provider_id'] : null,
                'subscription_name'  => $data['subscription_name'] ?? 'Default Subscription',
                'currency'           => $data['currency'] ?? (function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'USD'),
                'billing_cycle'   => $data['billing_cycle'] ?? 'monthly',
                'recurring_amount'   => $data['recurring_amount'] ?? 0,
                'billing_total_cycles'         => $data['billing_total_cycles'] ?? 0,
                'trial_days'         => $data['trial_days'] ?? 0,
                'status'             => $data['status'],
                'expire_at'          => $data['next_billing_date'] ?? null,
                'trial_ends_at'      => $data['trial_days'] ? gmdate('Y-m-d H:i:s', strtotime("+{$data['trial_days']} days")) : null,
                'next_billing_date'  => $data['next_billing_date'] ?? null,
                'total_paid'         => $data['amount'] ?? 0,
                'amount'             => $data['amount'] ?? 0,
            ]);

            if (($data['generator'] ?? null) === 'active') {
                License::create([
                    'uuid'            => wp_generate_uuid4(),
                    'license_key'     => $generatedLicenseKey ?: LicenzoKey::generate(),
                    'customer_id'     => $data['customer_id'],
                    'subscription_id' => $subscription->id,
                    'status'          => 'active',
                    'activation_count' => 0,
                    'activation_limit' => 1,
                    'expire_date'     => $data['next_billing_date'] ?? null,
                    'support_till'    => gmdate('Y-m-d', strtotime('+1 month', time())),
                ]);
            }

            foreach ($data['products'] as $product) {
                static::addOrUpdateSubscriptionProduct(
                    $subscription->id,
                    $product,
                    $data['customer_id'],
                    $data['generator'] ?? null,
                    $generatedLicenseKey
                );
            }

            static::logEvent($subscription->id, $data['customer_id'], 'created', $subscription->toJson());

            DB::commit();
            return ['success' => true, 'subscription_id' => $subscription->id];
        } catch (\Exception $e) {
            DB::rollback();
            return ['success' => false, 'message' => 'Error creating subscription: ' . $e->getMessage()];
        }
    }

    /**
     * Update an existing subscription
     */
    public static function update(int $subscriptionId, array $data): array
    {
        if (empty($data['customer_id']) || empty($data['status']) || empty($data['products']) || !is_array($data['products'])) {
            return ['success' => false, 'message' => 'Invalid subscription data provided'];
        }

        try {
            $subscription = Subscription::find($subscriptionId);
            if (!$subscription) return ['success' => false, 'message' => 'Subscription not found'];

            DB::beginTransaction();

            $subscription->fill([
                'customer_id'       => $data['customer_id'],
                'provider'          => !empty($data['provider'])
                    ? ProviderAliasService::normalize((string) sanitize_text_field((string) $data['provider']), SubscriptionTransaction::PROVIDER_MANUAL)
                    : ($subscription->provider ?: SubscriptionTransaction::PROVIDER_MANUAL),
                'provider_id'       => array_key_exists('provider_id', $data)
                    ? (($data['provider_id'] === null || $data['provider_id'] === '') ? null : (string) $data['provider_id'])
                    : $subscription->provider_id,
                'subscription_name' => $data['subscription_name'] ?? $subscription->subscription_name,
                'currency'          => $data['currency'] ?? $subscription->currency,
                'billing_cycle'  => $data['billing_cycle'] ?? $subscription->billing_cycle,
                'recurring_amount'  => $data['recurring_amount'] ?? $subscription->recurring_amount,
                'billing_total_cycles'        => $data['billing_total_cycles'] ?? $subscription->billing_total_cycles,
                'trial_days'        => $data['trial_days'] ?? $subscription->trial_days,
                'status'            => $data['status'],
                'trial_ends_at'     => $data['trial_days'] ? gmdate('Y-m-d H:i:s', strtotime("+{$data['trial_days']} days")) : $subscription->trial_ends_at,
                'next_billing_date' => $data['next_billing_date'] ?? $subscription->next_billing_date,
                'expire_at'         => $data['next_billing_date'] ?? $subscription->next_billing_date,
                'amount'            => $data['amount'] ?? $subscription->amount,
                'total_paid'        => $data['amount'] ?? $subscription->total_paid,
            ])->save();

            $license = License::where('subscription_id', $subscription->id)->first();
            if ($license) {
                $license->update([
                    'expire_date' => $data['next_billing_date'] ?? $license->expire_date,
                    'status'      => $data['status'] === 'active' ? 'active' : 'inactive',
                ]);
            }

            $licenseProducts = LicenseProduct::whereHas('license', function ($q) use ($subscriptionId) {
                $q->where('subscription_id', $subscriptionId);
            })->get();
            foreach ($licenseProducts as $lp) {
                $lp->update([
                    'expire_date' => $data['next_billing_date'] ?? $lp->expire_date,
                ]);
            }

            static::syncSubscriptionProducts(
                $subscription->id,
                $data['products'],
                $data['customer_id'],
                $data['generator'] ?? null,
                sanitize_text_field((string) ($data['generated_license_key'] ?? ''))
            );

            static::logEvent($subscription->id, $data['customer_id'], 'updated', json_encode($data));

            DB::commit();
            return ['success' => true];
        } catch (\Exception $e) {
            DB::rollback();
            return ['success' => false, 'message' => 'Error updating subscription: ' . $e->getMessage()];
        }
    }

    /**
     * Add a transaction to a subscription with deduplication guard.
     */
    public static function addTransaction(int $subscriptionId, array $data): array
    {
        $subscription = Subscription::find($subscriptionId);
        if (!$subscription) return ['success' => false, 'message' => 'Subscription not found'];

        try {
            $status = SubscriptionTransaction::normalizeStatus($data['status'] ?? SubscriptionTransaction::STATUS_PENDING);
            $type = $data['transaction_type'] ?? SubscriptionTransaction::TYPE_PURCHASE;
            $occurredAt = $data['transaction_date'] ?? gmdate('Y-m-d H:i:s');
            $currency = strtoupper((string) ($subscription->currency ?: (function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'USD')));
            $factor = (int) pow(10, MoneyCast::minorUnits($currency));
            $amountCents = (int) round((float) ($data['amount'] ?? 0) * $factor);
            $chargeKey = sprintf('manual_%d_%s_%d_%s', $subscriptionId, $type, $amountCents, gmdate('Ymd', strtotime($occurredAt)));

            $transaction = static::createOrGetLedgerTransaction([
                'provider'           => SubscriptionTransaction::PROVIDER_MANUAL,
                'provider_charge_id' => $chargeKey,
                'transaction_type'   => $type,
                'subscription_id'    => $subscription->id,
                'customer_id'        => $subscription->customer_id,
                'total'              => round((float) ($data['amount'] ?? 0), 2),
                'status'             => $status,
                'currency'           => $currency,
                'description'        => $data['notes'] ?? ($data['description'] ?? null),
                'meta'               => wp_json_encode($data),
            ]);

            static::logEvent($subscription->id, $subscription->customer_id, 'payment_succeeded', $transaction->toJson(), $transaction->id, 'transaction');

            return ['success' => true, 'transaction_id' => $transaction->id];
        } catch (\Exception $e) {
            return ['success' => false, 'message' => 'Error adding transaction: ' . $e->getMessage()];
        }
    }

    /**
     * Record invoice payment and create next draft installment when needed.
     */
    public static function recordInvoicePayment(int $subscriptionId, int $invoiceId, float $amount): array
    {
        if ($amount <= 0) {
            return ['success' => false, 'message' => 'Amount must be greater than zero.', 'status' => 422];
        }

        $subscription = Subscription::find($subscriptionId);
        if (!$subscription) {
            return ['success' => false, 'message' => 'Subscription not found', 'status' => 404];
        }

        $invoice = Invoice::where('subscription_id', $subscriptionId)->find($invoiceId);
        if (!$invoice) {
            return ['success' => false, 'message' => 'Invoice not found', 'status' => 404];
        }

        DB::beginTransaction();
        try {
            $invoiceService = new InvoiceService();
            $paidInvoice = $invoiceService->markAsPaid($invoice->id, $amount);
            if (!$paidInvoice) {
                DB::rollback();
                return ['success' => false, 'message' => 'Unable to update invoice payment state.', 'status' => 500];
            }

            $invoiceCurrency = strtoupper((string) ($invoice->currency ?: 'USD'));
            $paymentTxn = static::createOrGetLedgerTransaction([
                'provider' => SubscriptionTransaction::PROVIDER_MANUAL,
                'provider_charge_id' => sprintf('invoice_payment_%d_%d', $invoice->id, (int) round($amount * pow(10, MoneyCast::minorUnits($invoiceCurrency)))),
                'transaction_type' => SubscriptionTransaction::TYPE_CHARGE,
                'subscription_id' => $subscription->id,
                'customer_id' => $subscription->customer_id,
                'invoice_id' => $invoice->id,
                'status' => SubscriptionTransaction::STATUS_SUCCESS,
                'currency' => $invoiceCurrency,
                'total' => round($amount, 2),
                'description' => sprintf('Installment payment for invoice #%s', (string) $invoice->invoice_number),
                'meta' => wp_json_encode(['source' => 'invoice_payment']),
            ]);

            $nextInvoice = null;
            if (
                $subscription->collection_method === Subscription::COLLECTION_SEND_INVOICE &&
                (int) $invoice->installment_total > 1 &&
                (int) $invoice->installment_index < (int) $invoice->installment_total
            ) {
                $nextIndex = (int) $invoice->installment_index + 1;

                $existing = Invoice::where('subscription_id', $subscription->id)
                    ->where('billing_cycle_no', (int) $invoice->billing_cycle_no)
                    ->where('installment_index', $nextIndex)
                    ->first();

                if ($existing) {
                    $nextInvoice = $existing;
                } else {
                    $nextInvoice = $invoiceService->createDraft([
                        'subscription_id' => $subscription->id,
                        'customer_id' => $subscription->customer_id,
                        'provider' => 'licenzo',
                        'invoice_number' => sprintf('%s-%d', (string) $invoice->invoice_number, $nextIndex),
                        'billing_cycle_no' => (int) $invoice->billing_cycle_no,
                        'installment_index' => $nextIndex,
                        'installment_total' => (int) $invoice->installment_total,
                        'collection_method' => Invoice::COLLECTION_SEND_INVOICE,
                        'currency' => $invoiceCurrency,
                        'period_start' => $invoice->period_start,
                        'period_end' => $invoice->period_end,
                        'due_at' => $invoice->due_at,
                        'snapshot' => ['source_invoice_id' => $invoice->id],
                    ]);

                    $invoiceService->addLineItem($nextInvoice->id, [
                        'line_type' => InvoiceItem::LINE_SUBSCRIPTION,
                        'reference_type' => 'invoice',
                        'reference_id' => (int) $invoice->id,
                        'description' => sprintf('Installment %d of %d', $nextIndex, (int) $invoice->installment_total),
                        'quantity' => 1,
                        'unit_amount' => round((float) $invoice->total, 2),
                        'line_total' => round((float) $invoice->total, 2),
                        'currency' => $invoiceCurrency,
                    ]);
                    $nextInvoice = $invoiceService->recalculateTotals($nextInvoice->id);
                }
            }

            DB::commit();
            return [
                'success' => true,
                'invoice' => $paidInvoice,
                'transaction' => $paymentTxn,
                'next_invoice' => $nextInvoice,
            ];
        } catch (\Throwable $e) {
            DB::rollback();
            throw $e;
        }
    }

    private const STATUS_ON_HOLD = 'on_hold';

    public static function pause(int $subscriptionId): array
    {
        $subscription = Subscription::find($subscriptionId);
        if (!$subscription) {
            return ['success' => false, 'message' => 'Subscription not found'];
        }

        if ($subscription->status !== Subscription::STATUS_ACTIVE) {
            return ['success' => false, 'message' => 'Only active subscriptions can be paused'];
        }

        DB::beginTransaction();
        try {
            $subscription->update(['status' => Subscription::STATUS_PAUSED]);
            static::syncLicenseStatuses($subscriptionId, License::STATUS_SUSPENDED);
            static::logEvent(
                $subscriptionId,
                $subscription->customer_id,
                'paused',
                wp_json_encode(['new_status' => Subscription::STATUS_PAUSED])
            );
            DB::commit();
        } catch (\Throwable $e) {
            DB::rollback();
            throw $e;
        }

        $subscription->load('customer');
        Action::do('notification_subscription_paused', [
            'source' => 'subscription_service',
            'subscription' => $subscription->toArray(),
            'customer' => $subscription->customer ? $subscription->customer->toArray() : [],
        ]);

        return $subscription->fresh()->toArray();
    }

    public static function resume(int $subscriptionId): array
    {
        $subscription = Subscription::find($subscriptionId);
        if (!$subscription) {
            return ['success' => false, 'message' => 'Subscription not found'];
        }

        $allowedStatuses = [Subscription::STATUS_PAUSED, static::STATUS_ON_HOLD];
        if (!in_array($subscription->status, $allowedStatuses, true)) {
            return ['success' => false, 'message' => 'Only paused or on-hold subscriptions can be resumed'];
        }

        DB::beginTransaction();
        try {
            $subscription->update(['status' => Subscription::STATUS_ACTIVE]);
            static::syncLicenseStatuses($subscriptionId, License::STATUS_ACTIVE);
            static::logEvent(
                $subscriptionId,
                $subscription->customer_id,
                'resumed',
                wp_json_encode(['new_status' => Subscription::STATUS_ACTIVE])
            );
            DB::commit();
        } catch (\Throwable $e) {
            DB::rollback();
            throw $e;
        }

        $subscription->load('customer');
        Action::do('notification_subscription_resumed', [
            'source' => 'subscription_service',
            'subscription' => $subscription->toArray(),
            'customer' => $subscription->customer ? $subscription->customer->toArray() : [],
        ]);

        return $subscription->fresh()->toArray();
    }

    public static function cancel(int $subscriptionId, ?string $reason = null): array
    {
        $subscription = Subscription::find($subscriptionId);
        if (!$subscription) {
            return ['success' => false, 'message' => 'Subscription not found'];
        }

        $terminalStatuses = [Subscription::STATUS_CANCELED, Subscription::STATUS_EXPIRED];
        if (in_array($subscription->status, $terminalStatuses, true)) {
            return ['success' => false, 'message' => 'Subscription is already canceled or expired'];
        }

        DB::beginTransaction();
        try {
            $subscription->update([
                'status' => Subscription::STATUS_CANCELED,
                'canceled_at' => gmdate('Y-m-d H:i:s'),
            ]);
            static::syncLicenseStatuses($subscriptionId, License::STATUS_INACTIVE);
            static::logEvent(
                $subscriptionId,
                $subscription->customer_id,
                'canceled',
                wp_json_encode(['new_status' => Subscription::STATUS_CANCELED, 'reason' => $reason])
            );
            DB::commit();
        } catch (\Throwable $e) {
            DB::rollback();
            throw $e;
        }

        $subscription->load('customer');
        Action::do('notification_subscription_canceled', [
            'source' => 'subscription_service',
            'subscription' => $subscription->toArray(),
            'customer' => $subscription->customer ? $subscription->customer->toArray() : [],
            'reason' => $reason,
        ]);

        return $subscription->fresh()->toArray();
    }

    /* ------------------ Helper Methods ------------------ */

    private static function syncLicenseStatuses(int $subscriptionId, string $status): void
    {
        License::where('subscription_id', $subscriptionId)->update(['status' => $status]);
    }

    private static function createOrGetLedgerTransaction(array $attributes): SubscriptionTransaction
    {
        $existing = SubscriptionTransaction::where('provider', $attributes['provider'])
            ->where('provider_charge_id', $attributes['provider_charge_id'])
            ->first();

        if ($existing) {
            return $existing;
        }

        try {
            SubscriptionTransaction::create(array_merge([
                'uuid' => wp_generate_uuid4(),
                'provider_id' => null,
                'order_id' => null,
                'order_type' => null,
                'payment_method' => SubscriptionTransaction::PROVIDER_MANUAL,
                'payment_mode' => SubscriptionTransaction::PAYMENT_MODE_LIVE,
                'rate' => 1,
            ], $attributes));
        } catch (\Throwable $e) {
            // Fallback to deterministic lookup by unique provider charge key.
        }

        $persisted = SubscriptionTransaction::where('provider', $attributes['provider'])
            ->where('provider_charge_id', $attributes['provider_charge_id'])
            ->first();

        if ($persisted) {
            return $persisted;
        }

        throw new \RuntimeException('Unable to persist transaction ledger row.');
    }

    private static function resolveVariationAmount(ProductVariation $variation): float
    {
        $sale = (float) ($variation->sale_price ?? 0.0);
        if ($sale > 0) {
            return $sale;
        }

        return (float) ($variation->regular_price ?? 0.0);
    }

    private static function addOrUpdateSubscriptionProduct(
        int $subscriptionId,
        array $product,
        int $customerId,
        ?string $generator = null,
        ?string $generatedLicenseKey = null
    )
    {
        $baseProduct   = Product::find($product['product_id']);
        $baseVariation = ProductVariation::find($product['variation_id']);

        $subProduct = SubscriptionProduct::where('subscription_id', $subscriptionId)
            ->where('product_id', $product['product_id'])
            ->where('variation_id', $product['variation_id'])
            ->first();

        $productData = [
            'subscription_id' => $subscriptionId,
            'product_id'      => $product['product_id'],
            'variation_id'    => $product['variation_id'],
            'provider'        => $baseProduct->provider ?? null,
            'provider_id'     => $baseVariation->provider_id ?? ($baseProduct->provider_id ?? null),
            'product_name'    => $baseProduct->name ?? '',
            'variation_name'  => $baseVariation->name ?? '',
            'currency'        => $baseVariation->currency ?? 'USD',
            'price'           => floatval(($baseVariation->sale_price ?? null) !== null
                ? $baseVariation->sale_price
                : ($baseVariation->regular_price ?? 0)),
            'quantity'        => 1,
            'product_data'    => json_encode($baseProduct),
            'variation_data'  => json_encode($baseVariation),
        ];

        if ($subProduct) {
            $subProduct->update($productData);
        } else {
            SubscriptionProduct::create($productData);
        }

        // Generate license if generator is active
        if ($generator === 'active') {
            static::generateLicenseProducts(
                $subscriptionId,
                $customerId,
                $product,
                $baseProduct,
                $baseVariation,
                $generatedLicenseKey
            );
        }
    }

    private static function syncSubscriptionProducts(
        int $subscriptionId,
        array $products,
        int $customerId,
        ?string $generator = null,
        ?string $generatedLicenseKey = null
    )
    {
        $productIds   = array_column($products, 'product_id');
        $variationIds = array_column($products, 'variation_id');

        $removedProducts = SubscriptionProduct::where('subscription_id', $subscriptionId)
            ->whereNotIn('product_id', $productIds)
            ->whereNotIn('variation_id', $variationIds)
            ->get();

        foreach ($removedProducts as $removed) {
            $licenseProducts = LicenseProduct::where('product_id', $removed->product_id)
                ->where('variation_id', $removed->variation_id)
                ->whereHas('license', function ($q) use ($subscriptionId) {
                    $q->where('subscription_id', $subscriptionId);
                })
                ->get();

            foreach ($licenseProducts as $lp) {
                $lp->update(['expire_date' => gmdate('Y-m-d', time())]);
                $lp->license->update(['status' => 'inactive']);
            }
        }

        SubscriptionProduct::where('subscription_id', $subscriptionId)
            ->whereNotIn('product_id', $productIds)
            ->whereNotIn('variation_id', $variationIds)
            ->delete();

        foreach ($products as $product) {
            static::addOrUpdateSubscriptionProduct(
                $subscriptionId,
                $product,
                $customerId,
                $generator,
                $generatedLicenseKey
            );
        }
    }


    private static function generateLicenseProducts(
        int $subscriptionId,
        int $customerId,
        array $product,
        $baseProduct,
        $baseVariation,
        ?string $generatedLicenseKey = null
    )
    {
        $license = License::where('subscription_id', $subscriptionId)
            ->first();

        $subscription = Subscription::find($subscriptionId);
        if (!$subscription) {
            throw new \InvalidArgumentException('Subscription not found for license generation.');
        }

        if (!$license) {
            $license = License::create([
                'uuid'             => wp_generate_uuid4(),
                'license_key'      => $generatedLicenseKey ?: LicenzoKey::generate(),
                'customer_id'      => $customerId,
                'subscription_id'  => $subscriptionId,
                'status'           => 'active',
                'activation_count' => 0,
                'activation_limit' => 1,
                'expire_date'      => $subscription->expire_at ? gmdate('Y-m-d', strtotime($subscription->expire_at)) : null,
                'support_till'     => gmdate('Y-m-d', strtotime('+1 month', time())),
            ]);
        }

        LicenseProduct::create([
            'license_id'      => $license->id,
            'product_id'      => $product['product_id'],
            'variation_id'    => $product['variation_id'],
            'product_name'    => $baseProduct->name ?? '',
            'variation_name'  => $baseVariation->name ?? '',
            'started_at'      => gmdate('Y-m-d'),
            'expire_date'     => $subscription->expire_at ? gmdate('Y-m-d', strtotime($subscription->expire_at)) : null,
            'activation_count' => 0,
        ]);
    }

    private static function logEvent(int $subscriptionId, int $customerId, string $eventType, string $eventData, ?int $relatedId = null, ?string $relatedType = null)
    {
        SubscriptionEvent::create([
            'subscription_id' => $subscriptionId,
            'customer_id'     => $customerId,
            'event_type'      => $eventType,
            'event_source'    => 'system',
            'event_data'      => $eventData,
            'related_id'      => $relatedId,
            'related_type'    => $relatedType,
            'status'          => 'processed',
            'triggered_by'    => get_current_user_id(),
        ]);
    }
}
