<?php

namespace Licenzo\Http\Controller\Api\V1;

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

use Licenzo\Framework\Http\Request;
use Licenzo\Http\Traits\HandlesErrors;
use Licenzo\Model\License;
use Licenzo\Model\LicenseActivation;
use Licenzo\Model\LicenseProduct;
use Licenzo\Model\Product;
use Licenzo\Model\ProductVariation;
use Licenzo\Services\ActivationTokenService;
use Licenzo\Services\LicenseService;
use Licenzo\Services\Updates\ProductUpdateService;

/**
 * Header-Based Activation Controller
 *
 * Handles license activation, validation, and deactivation using
 * HTTP headers instead of URL path parameters.
 *
 * Activation:  POST /activate  — headers: License-Key, Fingerprint, Product (Variation optional)
 * Validate:    POST /validate  — headers: Activation-Token, Fingerprint
 * Deactivate:  POST /deactivate — headers: Activation-Token, Fingerprint
 */
class ActivationController
{
    use HandlesErrors;

    private LicenseService $licenseService;

    public function __construct(?LicenseService $licenseService = null)
    {
        $this->licenseService = $licenseService ?? new LicenseService();
    }

    /**
     * Activate a license using header-based credentials.
     *
     * Headers: X-Licenzo-License-Key, X-Licenzo-Fingerprint, X-Licenzo-Product
     */
    public function activate(Request $request): array
    {
        try {
            $licenseKey = $this->requireHeader($request, ActivationTokenService::HEADER_LICENSE_KEY);
            $fingerprint = $this->requireHeader($request, ActivationTokenService::HEADER_FINGERPRINT);
            $productUuid = $this->requireHeader($request, ActivationTokenService::HEADER_PRODUCT_UUID);

            $license = License::where('license_key', sanitize_text_field($licenseKey))->first();
            if (!$license) {
                return $this->notFoundError('License not found');
            }

            $product = Product::where('uuid', sanitize_text_field($productUuid))->first();
            if (!$product) {
                return $this->notFoundError('Product not found');
            }

            $variation = $this->resolveVariation($license, $product);
            if ($variation === null) {
                return $this->validationError(
                    ['variation' => 'Could not resolve variation for this license and product'],
                    'Variation resolution failed'
                );
            }

            $activationData = $request->getSafe([
                'instance_identifier' => 'sanitize_text_field',
                'site_url' => 'esc_url_raw',
                'home_url' => 'esc_url_raw',
            ]);

            if (empty($activationData['instance_identifier'])) {
                $activationData['instance_identifier'] = sanitize_text_field($fingerprint);
            }

            $activationData['fingerprint'] = sanitize_text_field($fingerprint);
            $activationData['activation_method'] = LicenseActivation::METHOD_TOKEN_BASED;

            $meta = $request->get('meta', null);
            if (is_string($meta) && $meta !== '') {
                $decoded = json_decode($meta, true);
                if (is_array($decoded)) {
                    $activationData['meta'] = $decoded;
                }
            } elseif (is_array($meta)) {
                $activationData['meta'] = $meta;
            }

            $activation = $this->licenseService->activateLicense($license, $product, $variation, $activationData);

            return $this->successResponse(
                $this->activationResponse($license, $activation, $variation),
                'License activated'
            );
        } catch (\Throwable $e) {
            return $this->handleError($e, 'Activation failed', 403);
        }
    }

    /**
     * Validate an activation using header-based activation token.
     *
     * Headers: X-Licenzo-Activation-Token, X-Licenzo-Fingerprint
     */
    public function validate(Request $request): array
    {
        try {
            $token = $this->requireHeader($request, ActivationTokenService::HEADER_ACTIVATION_TOKEN);
            $fingerprint = $this->requireHeader($request, ActivationTokenService::HEADER_FINGERPRINT);

            $result = $this->licenseService->validateByToken($token, sanitize_text_field($fingerprint));

            if (!$result['valid']) {
                $response = [
                    'success' => false,
                    'message' => $result['error'] ?? 'Validation failed',
                    'status'  => 403,
                ];
                if (!empty($result['code'])) {
                    $response['code'] = $result['code'];
                }
                return $response;
            }

            $activation = $result['activation'];
            $variation = ProductVariation::find((int) $activation->variation_id);

            return $this->successResponse(
                $this->activationResponse($result['license'], $activation, $variation),
                'License is valid'
            );
        } catch (\Throwable $e) {
            return $this->handleError($e, 'Validation failed', 403);
        }
    }

    /**
     * Deactivate an activation using header-based activation token.
     *
     * Headers: X-Licenzo-Activation-Token, X-Licenzo-Fingerprint
     */
    public function deactivate(Request $request): array
    {
        try {
            $token = $this->requireHeader($request, ActivationTokenService::HEADER_ACTIVATION_TOKEN);
            $fingerprint = $this->requireHeader($request, ActivationTokenService::HEADER_FINGERPRINT);

            $result = $this->licenseService->deactivateByToken($token, sanitize_text_field($fingerprint));

            if (!$result['success']) {
                $response = [
                    'success' => false,
                    'message' => $result['error'] ?? 'Deactivation failed',
                    'status'  => 403,
                ];
                if (!empty($result['code'])) {
                    $response['code'] = $result['code'];
                }
                return $response;
            }

            return $this->successResponse(null, 'License deactivated');
        } catch (\Throwable $e) {
            return $this->handleError($e, 'Deactivation failed', 403);
        }
    }

    /**
     * Check for product updates using header-based activation token.
     *
     * Headers: X-Licenzo-Activation-Token, X-Licenzo-Fingerprint
     * Optional: X-Licenzo-Channel
     */
    public function updateCheck(Request $request): array
    {
        try {
            $token = $this->requireHeader($request, ActivationTokenService::HEADER_ACTIVATION_TOKEN);
            $fingerprint = $this->requireHeader($request, ActivationTokenService::HEADER_FINGERPRINT);

            $result = $this->licenseService->validateByToken($token, sanitize_text_field($fingerprint));
            if (!$result['valid']) {
                $response = [
                    'success' => false,
                    'message' => $result['error'] ?? 'Token validation failed',
                    'status'  => 403,
                ];
                if (!empty($result['code'])) {
                    $response['code'] = $result['code'];
                }
                return $response;
            }

            $license = $result['license'];
            $activation = $result['activation'];

            $product = Product::find((int) $activation->product_id);
            $variation = ProductVariation::find((int) $activation->variation_id);
            if (!$product || !$variation) {
                return $this->notFoundError('Product or variation not found');
            }

            $context = $this->buildUpdateContext($request);

            $updateService = new ProductUpdateService();
            $data = $updateService->checkForUpdate(
                $license->license_key,
                (string) $product->uuid,
                (string) $variation->uuid,
                $context
            );

            return ['success' => true, 'data' => $data];
        } catch (\Throwable $e) {
            return $this->handleError($e, 'Failed to check for update', 403);
        }
    }

    /**
     * Get product update info using header-based activation token.
     *
     * Headers: X-Licenzo-Activation-Token, X-Licenzo-Fingerprint
     * Optional: X-Licenzo-Channel
     */
    public function updateInfo(Request $request): array
    {
        try {
            $token = $this->requireHeader($request, ActivationTokenService::HEADER_ACTIVATION_TOKEN);
            $fingerprint = $this->requireHeader($request, ActivationTokenService::HEADER_FINGERPRINT);

            $result = $this->licenseService->validateByToken($token, sanitize_text_field($fingerprint));
            if (!$result['valid']) {
                $response = [
                    'success' => false,
                    'message' => $result['error'] ?? 'Token validation failed',
                    'status'  => 403,
                ];
                if (!empty($result['code'])) {
                    $response['code'] = $result['code'];
                }
                return $response;
            }

            $license = $result['license'];
            $activation = $result['activation'];

            $product = Product::find((int) $activation->product_id);
            $variation = ProductVariation::find((int) $activation->variation_id);
            if (!$product || !$variation) {
                return $this->notFoundError('Product or variation not found');
            }

            $context = $this->buildUpdateContext($request);

            $updateService = new ProductUpdateService();
            $data = $updateService->getUpdateInfo(
                $license->license_key,
                (string) $product->uuid,
                (string) $variation->uuid,
                $context
            );

            return ['success' => true, 'data' => $data];
        } catch (\Throwable $e) {
            return $this->handleError($e, 'Failed to retrieve update information', 403);
        }
    }

    /**
     * Build the update context from body params + X-Licenzo-Channel header.
     * Header takes precedence over body 'channel' value.
     *
     * @return array<string,mixed>
     */
    private function buildUpdateContext(Request $request): array
    {
        $context = $request->getSafe([
            'current_version' => 'sanitize_text_field',
            'installed_version' => 'sanitize_text_field',
            'version' => 'sanitize_text_field',
            'channel' => 'sanitize_text_field',
            'site_url' => 'esc_url_raw',
            'home_url' => 'esc_url_raw',
            'plugin' => 'sanitize_text_field',
            'slug' => 'sanitize_text_field',
        ]);

        $headerChannel = $this->optionalHeader($request, ActivationTokenService::HEADER_CHANNEL);
        if ($headerChannel !== null) {
            $context['channel'] = sanitize_text_field($headerChannel);
        }

        return $context;
    }

    /**
     * Extract and validate a required header value.
     *
     * @throws \InvalidArgumentException If the header is missing or empty.
     */
    private function requireHeader(Request $request, string $headerName): string
    {
        $value = $request->header($headerName);
        if ($value === null || (is_string($value) && trim($value) === '')) {
            throw new \InvalidArgumentException(
                sprintf('Missing required header: %s', esc_html($headerName))
            );
        }

        return (string) $value;
    }

    /**
     * Extract an optional header value (returns null when absent).
     */
    private function optionalHeader(Request $request, string $headerName): ?string
    {
        $value = $request->header($headerName);
        if ($value === null || (is_string($value) && trim($value) === '')) {
            return null;
        }

        return (string) $value;
    }

    /**
     * Resolve variation from LicenseProduct when not provided by the client.
     * Returns null when zero or multiple rows exist (ambiguous).
     */
    private function resolveVariation(License $license, Product $product): ?ProductVariation
    {
        $rows = LicenseProduct::where('license_id', $license->id)
            ->where('product_id', $product->id)
            ->get();

        if (count($rows) !== 1) {
            return null;
        }

        $variationId = (int) $rows[0]->variation_id;
        if ($variationId < 1) {
            return null;
        }

        return ProductVariation::find($variationId);
    }

    /**
     * Build the activation/validation response payload with plan info.
     */
    private function activationResponse(
        License $license,
        ?LicenseActivation $activation = null,
        ?ProductVariation $variation = null
    ): ?array {
        if (!$activation) {
            return null;
        }

        $isLifetime = in_array(
            $variation ? $variation->billing_cycle : '',
            [ProductVariation::PRICE_CYCLE_LIFETIME, ProductVariation::PRICE_CYCLE_ONETIME],
            true
        );

        return [
            'activation_token' => $activation->activation_hash,
            'activation_id'    => $activation->id,
            'license_key'      => $license->license_key,
            'license'          => [
                'status'      => $license->status,
                'plan'        => $variation ? (string) $variation->name : '',
                'expires_at'  => $license->expire_date
                    ? gmdate('Y-m-d', strtotime($license->expire_date))
                    : null,
                'is_lifetime' => $isLifetime,
            ],
            'activations'      => [
                'used'  => (int) $license->activation_count,
                'limit' => (int) $license->activation_limit,
            ],
            'support_end_date' => $license->support_till
                ? gmdate('Y-m-d', strtotime($license->support_till))
                : null,
        ];
    }
}
