<?php

namespace Licenzo\Services\Notification;

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

use Licenzo\Helper\Settings\EmailNotificationSetting;
use Licenzo\Model\Customer;
use Licenzo\Model\License;
use Licenzo\Model\LicenseActivation;
use Licenzo\Model\Product;
use Licenzo\Model\ProductVariation;
use Licenzo\Services\Email\Email;
use Licenzo\Services\ShortcodeParser\Parser;

class EmailNotificationService
{
    private const FOOTER_TEXT = 'Powered by Licenzo';

    /**
     * @var Email
     */
    private $mailer;

    /**
     * @var array<string,mixed>
     */
    private $settings;

    private function __construct(Email $mailer, array $settings)
    {
        $this->mailer = $mailer;
        $this->settings = $settings;
    }

    public static function factory(): self
    {
        return new static(Email::fromDefault(), EmailNotificationSetting::all());
    }

    public function sendCustomerWelcome(array $payload = []): bool
    {
        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);

        return $this->dispatch('customer_welcome', $recipient, $attributes);
    }

    public function sendLicenseActivation(array $payload = []): bool
    {
        $context = $this->resolveContext($payload);
        if (empty($context['license'])) {
            return false;
        }

        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);

        return $this->dispatch('license_activation', $recipient, $attributes);
    }

    public function sendMaximumActivations(array $payload = []): bool
    {
        $context = $this->resolveContext($payload);
        if (empty($context['license'])) {
            return false;
        }

        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);

        return $this->dispatch('maximum_activations', $recipient, $attributes);
    }

    public function sendLicenseExpiryWarning(array $payload = []): bool
    {
        $context = $this->resolveContext($payload);
        if (empty($context['license'])) {
            return false;
        }

        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['days_until_expiry'] = (int) ($payload['days_until_expiry'] ?? 0);

        return $this->dispatch('license_expiry_warning', $recipient, $attributes);
    }

    public function sendRenewalReminder(array $payload = []): bool
    {
        $context = $this->resolveContext($payload);

        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;
        $attributes['next_billing_date'] = $this->scalarString($payload, 'next_billing_date');

        return $this->dispatch('renewal_reminder', $recipient, $attributes);
    }

    public function sendPaymentFailedNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;
        $attributes['failed_payment_count'] = (int) ($payload['failed_payment_count'] ?? 0);
        $attributes['next_payment_attempt'] = $this->scalarString($payload, 'next_payment_attempt');

        return $this->dispatch('payment_failed', $recipient, $attributes);
    }

    public function sendGracePeriodNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;
        $attributes['grace_end_date'] = $this->scalarString($payload, 'grace_end_date');
        $attributes['grace_days'] = (int) ($payload['grace_days'] ?? 0);

        return $this->dispatch('grace_period_started', $recipient, $attributes);
    }

    public function sendSubscriptionSuspendedNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;

        return $this->dispatch('subscription_suspended', $recipient, $attributes);
    }

    public function sendSubscriptionRenewedNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;
        $attributes['amount'] = $payload['amount'] ?? '';
        $attributes['currency'] = $payload['currency'] ?? '';

        return $this->dispatch('subscription_renewed', $recipient, $attributes);
    }

    public function sendSubscriptionPausedNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;

        return $this->dispatch('subscription_paused', $recipient, $attributes);
    }

    public function sendSubscriptionResumedNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;

        return $this->dispatch('subscription_resumed', $recipient, $attributes);
    }

    public function sendSubscriptionCanceledNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id'] = $subscriptionId;

        return $this->dispatch('subscription_canceled', $recipient, $attributes);
    }

    public function sendSubscriptionRequiresActionNotification(array $payload = []): bool
    {
        $subscriptionId = (int) ($payload['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            return false;
        }

        $context = $this->resolveContext($payload);
        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $attributes['subscription_id']  = $subscriptionId;
        $attributes['intent_id']        = (string) ($payload['intent_id'] ?? '');
        $attributes['client_secret']    = (string) ($payload['client_secret'] ?? '');
        $attributes['next_action_type'] = (string) ($payload['next_action_type'] ?? '');
        $attributes['amount_cents']     = $payload['amount_cents'] ?? '';
        $attributes['currency']         = $payload['currency'] ?? '';

        return $this->dispatch('subscription_requires_action', $recipient, $attributes);
    }

    public function sendLicenseExpiration(array $payload = []): bool
    {
        $context = $this->resolveContext($payload);
        if (empty($context['license'])) {
            return false;
        }

        $lockKey = $this->buildExpirationLockKey($context['license'], $payload);
        if ($lockKey !== '' && get_transient($lockKey)) {
            return true;
        }

        $recipient = $this->resolveRecipientEmail($context['customer'], $payload);
        if ($recipient === '') {
            return false;
        }

        $attributes = $this->buildAttributes($context, $payload);
        $sent = $this->dispatch('license_expiration', $recipient, $attributes);
        if ($sent && $lockKey !== '') {
            set_transient($lockKey, '1', DAY_IN_SECONDS);
        }

        return $sent;
    }

    private function dispatch(string $settingKey, string $recipient, array $attributes): bool
    {
        if ($recipient === '' || !$this->isEnabled($settingKey)) {
            return false;
        }

        $subjectTemplate = (string) ($this->settings[$settingKey]['subject'] ?? '');
        $bodyTemplate = (string) ($this->settings[$settingKey]['body'] ?? '');
        if ($subjectTemplate === '' || $bodyTemplate === '') {
            return false;
        }

        $subject = (string) Parser::parse($subjectTemplate, $attributes);
        $body = (string) Parser::parse($bodyTemplate, $attributes);
        $body = $this->appendFooter($body);

        $headers = $this->isHtmlContent($body) ? ['Content-Type: text/html; charset=UTF-8'] : [];

        return (bool) $this->mailer->send($recipient, $subject, $body, $headers);
    }

    private function isEnabled(string $settingKey): bool
    {
        return strtolower((string) ($this->settings[$settingKey]['enabled'] ?? 'no')) === 'yes';
    }

    private function appendFooter(string $body): string
    {
        $body = trim($body);
        if ($body === '') {
            return static::FOOTER_TEXT;
        }

        if (stripos($body, static::FOOTER_TEXT) !== false) {
            return $body;
        }

        return $body . "\n\n" . static::FOOTER_TEXT;
    }

    private function isHtmlContent(string $value): bool
    {
        return $value !== wp_strip_all_tags($value);
    }

    /**
     * @param array<string,mixed> $payload
     * @return array<string,array<string,mixed>>
     */
    private function resolveContext(array $payload): array
    {
        $license = $this->resolveEntityFromPayload($payload, 'license', 'license_id', License::class);
        $product = $this->resolveEntityFromPayload($payload, 'product', 'product_id', Product::class);
        $variation = $this->resolveEntityFromPayload($payload, 'variation', 'variation_id', ProductVariation::class);
        $activation = $this->resolveEntityFromPayload($payload, 'activation', 'activation_id', LicenseActivation::class);
        $customer = $this->resolveCustomerEntity($payload, $license);

        return [
            'customer' => $customer,
            'license' => $license,
            'product' => $product,
            'variation' => $variation,
            'activation' => $activation,
        ];
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function resolveEntityFromPayload(array $payload, string $entityKey, string $idKey, ?string $modelClass = null): array
    {
        $entity = $this->arrayValue($payload[$entityKey] ?? []);
        if (!empty($entity)) {
            return $entity;
        }

        $entityId = (int) ($payload[$idKey] ?? 0);
        if ($entityId <= 0 || !$modelClass) {
            return [];
        }

        $model = $modelClass::find($entityId);
        if (!$model || !method_exists($model, 'toArray')) {
            return [];
        }

        return $this->arrayValue($model->toArray());
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function resolveCustomerEntity(array $payload, array $license): array
    {
        $customer = $this->resolveEntityFromPayload($payload, 'customer', 'customer_id', Customer::class);
        if (!empty($customer)) {
            return $customer;
        }

        $customerId = (int) ($license['customer_id'] ?? 0);
        if ($customerId <= 0) {
            return [];
        }

        $model = Customer::find($customerId);
        if (!$model || !method_exists($model, 'toArray')) {
            return [];
        }

        return $this->arrayValue($model->toArray());
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function resolveRecipientEmail(array $customer, array $payload): string
    {
        $email = $this->scalarString($payload, 'customer_email');
        if ($email === '') {
            $email = sanitize_email((string) ($customer['email'] ?? ''));
        }

        return $email ?: '';
    }

    /**
     * @param array<string,array<string,mixed>> $context
     * @param array<string,mixed> $payload
     * @return array<string,mixed>
     */
    private function buildAttributes(array $context, array $payload): array
    {
        $customer = $context['customer'];
        $license = $context['license'];
        $product = $context['product'];
        $variation = $context['variation'];
        $activation = $context['activation'];

        $firstName = $this->scalarString($payload, 'customer_first_name');
        if ($firstName === '') {
            $firstName = trim((string) ($customer['first_name'] ?? ''));
        }

        $lastName = $this->scalarString($payload, 'customer_last_name');
        if ($lastName === '') {
            $lastName = trim((string) ($customer['last_name'] ?? ''));
        }

        $customerName = $this->scalarString($payload, 'customer_name');
        if ($customerName === '') {
            $customerName = trim($firstName . ' ' . $lastName);
        }
        if ($customerName === '') {
            $customerName = trim((string) ($customer['email'] ?? ''));
        }
        if ($customerName === '') {
            $customerName = 'Customer';
        }

        $activatedAt = $this->scalarString($payload, 'activated_date');
        if ($activatedAt === '') {
            $activatedAt = trim((string) ($activation['activated_at'] ?? ''));
        }

        $expireDate = $this->scalarStringOrMeta($payload, 'expire_date');
        if ($expireDate !== '') {
            $resolvedExpireDate = $expireDate;
        } elseif (!empty($license['expire_date'])) {
            $resolvedExpireDate = trim((string) $license['expire_date']);
        } else {
            $resolvedExpireDate = trim((string) ($activation['expire_date'] ?? ''));
        }

        $domain = $this->scalarStringOrMeta($payload, 'domain');
        if ($domain === '') {
            $domain = trim((string) ($activation['instance_identifier'] ?? ''));
        }

        return [
            'customer' => [
                'id' => (int) ($customer['id'] ?? 0),
                'name' => $customerName,
                'email' => (string) ($customer['email'] ?? ''),
                'first_name' => $firstName,
                'last_name' => $lastName,
            ],
            'license' => [
                'id' => (int) ($license['id'] ?? 0),
                'key' => trim((string) ($license['license_key'] ?? $license['key'] ?? '')),
                'domain' => $domain,
                'activated_date' => $activatedAt,
                'expire_date' => $resolvedExpireDate,
            ],
            'product' => [
                'id' => (int) ($product['id'] ?? 0),
                'name' => trim((string) ($product['name'] ?? '')),
            ],
            'variation' => [
                'id' => (int) ($variation['id'] ?? 0),
                'name' => trim((string) ($variation['name'] ?? '')),
            ],
            'activation' => [
                'id' => (int) ($activation['id'] ?? 0),
                'instance_identifier' => trim((string) ($activation['instance_identifier'] ?? '')),
                'activated_at' => trim((string) ($activation['activated_at'] ?? '')),
                'expire_date' => trim((string) ($activation['expire_date'] ?? '')),
            ],
        ];
    }

    /**
     * @param array<string,mixed> $license
     * @param array<string,mixed> $payload
     */
    private function buildExpirationLockKey(array $license, array $payload): string
    {
        $licenseId = (int) ($license['id'] ?? 0);
        if ($licenseId <= 0) {
            return '';
        }

        $reason = sanitize_key((string) ($payload['reason'] ?? 'license_expired'));
        $expireDate = $this->scalarStringOrMeta($payload, 'expire_date');
        if ($expireDate === '') {
            $expireDate = trim((string) ($license['expire_date'] ?? ''));
        }

        return 'licenzo_notify_expiration_' . md5((string) $licenseId . ':' . $reason . ':' . $expireDate);
    }

    /**
     * @return array<string,mixed>
     */
    private function arrayValue($value): array
    {
        return is_array($value) ? $value : [];
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function scalarString(array $payload, string $key): string
    {
        $value = $payload[$key] ?? '';
        if (!is_scalar($value)) {
            return '';
        }

        return trim((string) $value);
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function scalarStringOrMeta(array $payload, string $key): string
    {
        $direct = $this->scalarString($payload, $key);
        if ($direct !== '') {
            return $direct;
        }

        $meta = $this->arrayValue($payload['meta'] ?? []);
        return $this->scalarString($meta, $key);
    }
}
