<?php

namespace Licenzo\Http\Controller\Api\V1\Customers;

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

use Licenzo\Framework\Http\Request;
use Licenzo\Model\Customer;
use Licenzo\Model\License;
use Licenzo\Model\Subscription;
use Licenzo\Model\SubscriptionProduct;
use Licenzo\Model\LicenseEvent;
use Licenzo\Model\Product;
use Licenzo\Model\ProductVariation;
use Licenzo\Services\LicenseService;
use Licenzo\Services\Media\FileStreamer;
use Licenzo\Services\Updates\ProductReleaseService;
use Licenzo\Framework\Http\Response;

class CustomerController
{
    public function overview()
    {
        $customer = $this->currentCustomer();
        if (!$customer) {
            return $this->emptyOverview();
        }

        $id = $customer->id;

        $licensesCount = License::where('customer_id', $id)->count();
        $subscriptionsCount = Subscription::where('customer_id', $id)->count();
        $subscriptionProductsCount = SubscriptionProduct::query()
            ->whereHas('subscription', fn($q) => $q->where('customer_id', $id))
            ->count();
        $totalSpent = (float) Subscription::where('customer_id', $id)
            ->sum('total_paid') / 100;

        return [
            'success' => true,
            'data' => [
                'licenses_count' => $licensesCount,
                'subscriptions_count' => $subscriptionsCount,
                'subscription_products_count' => $subscriptionProductsCount,
                'total_spent' => $totalSpent,
            ],
        ];
    }

    public function licenses(Request $request)
    {
        $customer = $this->currentCustomer();
        if (!$customer) {
            return $this->emptyPaginated('licenses');
        }

        $search = $request->getSafe('search', 'sanitize_text_field');
        $status = $request->getSafe('status', 'sanitize_text_field');
        $perPage = (int) $request->getSafe('per_page', 'absint') ?: 10;

        $query = License::query()
            ->with('subscription.subscriptionProducts')
            ->where('customer_id', $customer->id)
            ->orderBy('created_at', 'desc');

        if (!empty($search)) {
            $query->where(function ($q) use ($search) {
                $q->where('license_key', 'like', "%{$search}%")
                    ->orWhereHas('subscription.subscriptionProducts', function ($spq) use ($search) {
                        $spq->where('product_name', 'like', "%{$search}%");
                    });
            });
        }

        if (!empty($status)) {
            $query->where('status', $status);
        }

        $licenses = $query->paginate($perPage);

        $licenses->getCollection()->transform(function ($license) {
            return [
                'id' => $license->id,
                'license_key' => $license->license_key,
                'product_list' => $license->subscription->subscriptionProducts->pluck('product_name')->toArray(),
                'status' => $license->status,
                'expire_date' => $license->expire_date,
                'activations' => $license->activation_count,
                'activation_limit' => $license->activation_limit,
                'billing_cycle' => $license->subscription->billing_cycle,
            ];
        });

        return [
            'success' => true,
            'licenses' => $licenses,
        ];
    }

    public function subscriptions(Request $request)
    {
        $customer = $this->currentCustomer();
        if (!$customer) {
            return $this->emptyPaginated('subscriptions');
        }

        $search = $request->getSafe('search', 'sanitize_text_field');
        $status = $request->getSafe('status', 'sanitize_text_field');
        $perPage = (int) $request->getSafe('per_page', 'absint') ?: 10;

        $query = Subscription::where('customer_id', $customer->id)
            ->with('subscriptionProducts')
            ->orderBy('created_at', 'desc');

        if (!empty($search)) {
            $query->where(function ($q) use ($search) {
                $q->where('subscription_name', 'like', "%{$search}%")
                    ->orWhereHas('subscriptionProducts', function ($spq) use ($search) {
                        $spq->where('product_name', 'like', "%{$search}%");
                    });
            });
        }

        if (!empty($status)) {
            $query->where('status', $status);
        }

        $subscriptions = $query->paginate($perPage);

        $subscriptions->getCollection()->transform(function ($subscription) {
            return [
                'id' => $subscription->id,
                'subscription_name' => $subscription->subscription_name,
                'product_list' => $subscription->subscriptionProducts->pluck('product_name')->toArray(),
                'status' => $subscription->status,
                'billing_cycle' => $subscription->billing_cycle,
                'recurring_total' => $subscription->recurring_amount,
                'from_date' => $subscription->created_at->toDateString(),
                'renewal_date' => $subscription->next_billing_date
                    ? $subscription->next_billing_date->toDateString()
                    : null,
            ];
        });

        return [
            'success' => true,
            'subscriptions' => $subscriptions,
        ];
    }

    /**
     * @param int $licenseId
     * @return array|Response
     */
    public function licenseActivations($licenseId)
    {
        $license = $this->findCustomerLicense((int) $licenseId);

        if (!$license) {
            return new Response([
                'success' => false,
                'message' => __('License not found.', 'licenzo'),
            ], 404);
        }

        $activations = $license->activations()
            ->orderBy('activated_at', 'desc')
            ->paginate(10);

        $activations->getCollection()->transform(function ($activation) {
            return [
                'id' => $activation->id,
                'instance_identifier' => $activation->instance_identifier,
                'product_name' => $activation->product_name,
                'variation_name' => $activation->variation_name,
                'status' => $activation->status,
                'ip_address' => $activation->ip_address,
                'activated_at' => $activation->activated_at,
                'deactivated_at' => $activation->deactivated_at,
            ];
        });

        return [
            'success' => true,
            'activations' => $activations,
        ];
    }

    /**
     * @param int $licenseId
     * @param int $activationId
     * @return array|Response
     */
    public function deactivateActivation($licenseId, $activationId)
    {
        $license = $this->findCustomerLicense((int) $licenseId);

        if (!$license) {
            return new Response([
                'success' => false,
                'message' => __('License not found.', 'licenzo'),
            ], 404);
        }

        try {
            $licenseService = new LicenseService();
            $licenseService->deactivateActivationById($license, (int) $activationId);

            return [
                'success' => true,
                'message' => __('Activation deactivated successfully.', 'licenzo'),
            ];
        } catch (\Exception $e) {
            return new Response([
                'success' => false,
                'message' => __('An error occurred', 'licenzo'),
            ], 422);
        }
    }

    /**
     * List downloadable products for the current customer.
     */
    public function downloads()
    {
        $customer = $this->currentCustomer();
        if (!$customer) {
            return ['success' => true, 'downloads' => []];
        }

        $subscriptionProducts = SubscriptionProduct::query()
            ->whereHas('subscription', fn($q) => $q->where('customer_id', $customer->id))
            ->with(['product', 'variation'])
            ->get();

        $releaseService = new ProductReleaseService();
        $seen = [];
        $downloads = [];

        foreach ($subscriptionProducts as $subProduct) {
            $product = $subProduct->product;
            $variation = $subProduct->variation;

            if (!$product || !$variation) {
                continue;
            }

            $key = $product->id . '-' . $variation->id;
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;

            $release = $releaseService->findLatestAvailableRelease($product, $variation);
            $downloadUrl = null;
            $version = null;
            $releaseDate = null;
            $changelog = null;

            if ($release && isset($release['selected_package'])) {
                $downloadUrl = rest_url('licenzo/v1/customer/downloads/' . (int) $product->id . '/' . (int) $variation->id . '/file');
                $version = $release['version'] ?? null;
                $releaseDate = $release['release_date'] ?? null;
                $changelog = $release['changelog'] ?? null;
            }

            if (!$downloadUrl) {
                $eddFile = $this->resolveEddDownloadFile($product);
                if ($eddFile) {
                    $downloadUrl = rest_url('licenzo/v1/customer/downloads/' . (int) $product->id . '/' . (int) $variation->id . '/file');
                }
            }

            $downloads[] = [
                'product_id' => (int) $product->id,
                'product_name' => (string) $product->name,
                'variation_id' => (int) $variation->id,
                'variation_name' => (string) $variation->name,
                'version' => $version,
                'release_date' => $releaseDate,
                'changelog' => $changelog,
                'download_url' => $downloadUrl ?: null,
                'has_download' => !empty($downloadUrl),
            ];
        }

        return [
            'success' => true,
            'downloads' => $downloads,
        ];
    }

    /**
     * Stream the latest release package file for a product/variation.
     *
     * @return array|Response|void
     */
    public function downloadFile(Request $request, $productId = null, $variationId = null)
    {
        $customer = $this->currentCustomer();
        if (!$customer) {
            return new Response(['success' => false, 'message' => __('Authentication required.', 'licenzo')], 401);
        }

        $productId = absint($productId ?: $request->get('productId', 0));
        $variationId = absint($variationId ?: $request->get('variationId', 0));

        if ($productId <= 0 || $variationId <= 0) {
            return new Response(['success' => false, 'message' => __('Invalid product or variation.', 'licenzo')], 400);
        }

        $subscriptionProduct = SubscriptionProduct::query()
            ->whereHas('subscription', fn($q) => $q->where('customer_id', $customer->id))
            ->where('product_id', $productId)
            ->where('variation_id', $variationId)
            ->first();

        if (!$subscriptionProduct) {
            return new Response(['success' => false, 'message' => __('Not entitled to this product.', 'licenzo')], 403);
        }

        $product = Product::find($productId);
        $variation = ProductVariation::find($variationId);

        if (!$product || !$variation) {
            return new Response(['success' => false, 'message' => __('Product or variation not found.', 'licenzo')], 404);
        }

        $releaseService = new ProductReleaseService();
        $release = $releaseService->findLatestAvailableRelease($product, $variation);

        if (!$release) {
            $eddFile = $this->resolveEddDownloadFile($product);
            if (!$eddFile) {
                return new Response(['success' => false, 'message' => __('No release available for download.', 'licenzo')], 404);
            }

            $this->logDownloadEvent($customer, $product, $variation, ['version' => null, 'release_date' => null, 'source' => 'edd'], $subscriptionProduct);
            $fileName = sanitize_file_name($eddFile['name'] ?: basename($eddFile['path']));
            $mimeType = (string) (wp_check_filetype($eddFile['path'])['type'] ?? 'application/octet-stream') ?: 'application/octet-stream';
            $fileSize = filesize($eddFile['path']);
            FileStreamer::stream($eddFile['path'], $fileName, $mimeType, $fileSize ? (int) $fileSize : null);
            return;
        }

        $package = $release['selected_package'] ?? $releaseService->getVariationPackage($release, $variation);
        if (!$package) {
            return new Response(['success' => false, 'message' => __('Release package not available.', 'licenzo')], 404);
        }

        $media = is_array($package['media'] ?? null) ? $package['media'] : [];
        $attachmentId = absint($media['attachment_id'] ?? 0);

        if ($attachmentId <= 0) {
            return new Response(['success' => false, 'message' => __('Package file not found.', 'licenzo')], 404);
        }

        $filePath = get_attached_file($attachmentId);
        if (!is_string($filePath) || $filePath === '' || !file_exists($filePath) || !is_readable($filePath)) {
            return new Response(['success' => false, 'message' => __('Package file could not be found on server.', 'licenzo')], 500);
        }

        $this->logDownloadEvent($customer, $product, $variation, $release, $subscriptionProduct);

        $fileName = sanitize_file_name((string) ($media['download_name'] ?? basename($filePath)));
        $mimeType = (string) ($media['mime_type'] ?? 'application/octet-stream');
        if ($mimeType === '') {
            $mimeType = 'application/octet-stream';
        }
        $fileSize = filesize($filePath);

        FileStreamer::stream($filePath, $fileName, $mimeType, $fileSize ? (int) $fileSize : null);
    }

    /**
     * Resolve the first EDD download file for a product linked to an EDD download.
     *
     * @return array{name: string, path: string, attachment_id: int}|null
     */
    private function resolveEddDownloadFile(Product $product): ?array
    {
        $provider = strtolower((string) ($product->provider ?? ''));
        if ($provider !== 'edd') {
            return null;
        }

        $eddDownloadId = (int) ($product->provider_id ?? 0);
        if ($eddDownloadId <= 0) {
            return null;
        }

        $files = get_post_meta($eddDownloadId, 'edd_download_files', true);
        if (!is_array($files) || empty($files)) {
            return null;
        }

        $file = reset($files);
        $attachmentId = absint($file['attachment_id'] ?? 0);
        $name = sanitize_text_field((string) ($file['name'] ?? ''));

        if ($attachmentId > 0) {
            $filePath = get_attached_file($attachmentId);
            if (is_string($filePath) && $filePath !== '' && file_exists($filePath) && is_readable($filePath)) {
                return ['name' => $name, 'path' => $filePath, 'attachment_id' => $attachmentId];
            }
        }

        $fileUrl = (string) ($file['file'] ?? '');
        if ($fileUrl !== '') {
            $uploadDir = wp_get_upload_dir();
            $baseUrl = trailingslashit((string) ($uploadDir['baseurl'] ?? ''));
            if ($baseUrl !== '' && str_starts_with($fileUrl, $baseUrl)) {
                $relativePath = substr($fileUrl, strlen($baseUrl));
                $absolutePath = trailingslashit((string) ($uploadDir['basedir'] ?? '')) . $relativePath;
                if (file_exists($absolutePath) && is_readable($absolutePath)) {
                    return ['name' => $name ?: basename($absolutePath), 'path' => $absolutePath, 'attachment_id' => $attachmentId];
                }
            }
        }

        return null;
    }

    private function logDownloadEvent(Customer $customer, Product $product, ProductVariation $variation, array $release, SubscriptionProduct $subscriptionProduct): void
    {
        try {
            $license = License::where('subscription_id', (int) $subscriptionProduct->subscription_id)
                ->where('customer_id', (int) $customer->id)
                ->first();

            LicenseEvent::logEvent([
                'license_id' => $license ? (int) $license->id : null,
                'product_id' => (int) $product->id,
                'variation_id' => (int) $variation->id,
                'product_name' => (string) $product->name,
                'variation_name' => (string) $variation->name,
                'event_type' => LicenseEvent::EVENT_DOWNLOADED,
                'event_source' => LicenseEvent::SOURCE_API,
                'event_data' => [
                    'download_source' => 'customer_portal',
                    'release_uuid' => (string) ($release['uuid'] ?? ''),
                    'version' => (string) ($release['version'] ?? ''),
                    'channel' => (string) ($release['channel'] ?? 'stable'),
                ],
                'ip_address' => sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? '')),
                'response_status' => LicenseEvent::STATUS_SUCCESS,
                'response_message' => 'File downloaded via customer portal',
                'triggered_by' => get_current_user_id(),
            ]);
        } catch (\Throwable $e) {
            // Logging failure must not block the download
        }
    }

    private function findCustomerLicense(int $licenseId): ?License
    {
        $customer = $this->currentCustomer();
        if (!$customer) {
            return null;
        }

        return License::where('id', $licenseId)
            ->where('customer_id', $customer->id)
            ->first();
    }

    /**
     * Cancel a subscription at the end of the current billing period.
     *
     * If the subscription is backed by a Stripe Subscription, cancellation is
     * forwarded to Stripe (cancel_at_period_end). Customer keeps access until
     * the period ends, then Stripe fires customer.subscription.deleted.
     */
    public function cancelSubscription(Request $request, $subscriptionId)
    {
        $customer = $this->currentCustomer();
        if (!$customer) {
            return new \WP_Error('not_found', 'Customer not found', ['status' => 404]);
        }

        $subscription = Subscription::where('id', (int) $subscriptionId)
            ->where('customer_id', $customer->id)
            ->first();

        if (!$subscription) {
            return new \WP_Error('not_found', 'Subscription not found', ['status' => 404]);
        }

        if ($subscription->status === Subscription::STATUS_CANCELED) {
            return ['success' => true, 'message' => 'Subscription is already canceled.'];
        }

        $settings = \Licenzo\Helper\Settings::getSubscriptionSettings();
        if (($settings['allow_customer_cancellation'] ?? 'yes') !== 'yes') {
            return new \WP_Error('forbidden', 'Subscription cancellation is not allowed. Please contact support.', ['status' => 403]);
        }

        $stripeSubId = (string) ($subscription->provider_subscription_id ?? '');
        $isStripeSub = str_starts_with($stripeSubId, 'sub_');

        if ($isStripeSub) {
            $canceled = \Licenzo\Modules\EDD\EddStripeSubscriptionService::cancelAtPeriodEnd($stripeSubId);
            if (!$canceled) {
                return new \WP_Error('stripe_error', 'Unable to cancel subscription. Please try again or contact support.', ['status' => 500]);
            }

            \Licenzo\Services\Logging\SubscriptionEvent::log($subscription, 'cancel_requested', null, [
                'source' => 'customer_portal',
                'mode'   => 'stripe_cancel_at_period_end',
            ]);

            return [
                'success' => true,
                'message' => 'Subscription will be canceled at the end of the current billing period.',
                'cancel_at_period_end' => true,
            ];
        }

        $subscription->status = Subscription::STATUS_CANCELED;
        $subscription->canceled_at = gmdate('Y-m-d H:i:s');
        $subscription->save();

        \Licenzo\Services\Logging\SubscriptionEvent::log($subscription, 'subscription_canceled', null, [
            'source' => 'customer_portal',
        ]);

        return [
            'success' => true,
            'message' => 'Subscription has been canceled.',
        ];
    }

    private function currentCustomer(): ?Customer
    {
        return Customer::where('user_id', get_current_user_id())->first();
    }

    private function emptyOverview(): array
    {
        return [
            'success' => true,
            'data' => [
                'licenses_count' => 0,
                'subscriptions_count' => 0,
                'subscription_products_count' => 0,
                'total_spent' => 0,
            ],
        ];
    }

    private function emptyPaginated(string $key): array
    {
        return [
            'success' => true,
            $key => [
                'data' => [],
                'total' => 0,
                'per_page' => 10,
                'current_page' => 1,
                'last_page' => 1,
            ],
        ];
    }
}
