<?php

namespace GriffinForms\Includes\Integrations\Mailchimp;

use GriffinForms\Log;
use GriffinForms\Settings;
use WP_Error;

class SyncService
{
    protected $client;
    protected $identity_store;
    protected $state_store;
    protected $log;
    protected $settings;

    public function __construct()
    {
        $this->client = new Client();
        $this->identity_store = new IdentityStore();
        $this->state_store = new SyncStateStore();
        $this->log = Log::getInstance();
        $this->settings = Settings::getInstance();
    }

    public function syncSubmission(array $job, array $context): void
    {
        $submission_id = isset($job['submission_id']) ? (int) $job['submission_id'] : 0;
        $form_id = isset($job['form_id']) ? (int) $job['form_id'] : 0;
        $author_user_id = isset($context['submission_meta']['author']) ? (int) $context['submission_meta']['author'] : 0;
        $owner_context = $this->identity_store->buildOwnerContext($submission_id, $form_id, $author_user_id);

        if ($submission_id <= 0 || $form_id <= 0) {
            return;
        }

        $integration = new Main();
        $effective = $integration->getEffectiveFormSettings($form_id);
        $api_key = trim((string) $this->settings->getOption('mailchimp_api_key', ''));
        $default_audience_id = trim((string) $this->settings->getOption('mailchimp_default_audience_id', ''));
        $default_status = sanitize_key((string) $this->settings->getOption('mailchimp_default_status', 'subscribed'));

        if ($default_status !== 'pending') {
            $default_status = 'subscribed';
        }

        if (($effective['mode'] ?? '') === 'disabled') {
            $this->persistSkippedState($submission_id, [
                'schema_version' => Contract::IDENTITY_SCHEMA_VERSION,
                'audience_id' => '',
                'subscription_status' => '',
                'owner' => $owner_context,
                'storage_strategy' => $this->buildStorageStrategy($owner_context),
                'reason' => 'form_disabled',
                'message' => 'Mailchimp sync skipped because the integration is disabled for this form.',
            ]);
            return;
        }

        $audience_id = sanitize_text_field((string) ($effective['audience_id'] ?? $default_audience_id));
        $subscription_status = sanitize_key((string) ($effective['subscription_status'] ?? $default_status));
        if ($subscription_status !== 'pending') {
            $subscription_status = 'subscribed';
        }

        if ($api_key === '' || $audience_id === '') {
            $this->persistFailureState($submission_id, [
                'schema_version' => Contract::IDENTITY_SCHEMA_VERSION,
                'audience_id' => $audience_id,
                'subscription_status' => $subscription_status,
                'owner' => $owner_context,
                'storage_strategy' => $this->buildStorageStrategy($owner_context),
                'last_error' => 'Mailchimp sync could not start because API credentials or audience configuration are incomplete.',
            ]);
            $this->log->add(
                'error',
                'submission',
                $submission_id,
                get_current_user_id(),
                'Mailchimp sync failed because API credentials or audience configuration are incomplete.',
                Contract::LOG_CATEGORY
            );
            return;
        }

        $email_field_id = isset($effective['subscriber_email_field']) ? (string) $effective['subscriber_email_field'] : '';
        $email = $this->extractEmailValue($context['field_values'][$email_field_id] ?? null);
        if ($email === '') {
            $this->persistSkippedState($submission_id, [
                'schema_version' => Contract::IDENTITY_SCHEMA_VERSION,
                'audience_id' => $audience_id,
                'subscription_status' => $subscription_status,
                'email_field_id' => $email_field_id,
                'owner' => $owner_context,
                'storage_strategy' => $this->buildStorageStrategy($owner_context),
                'reason' => 'missing_email',
                'message' => 'Mailchimp sync skipped because the mapped subscriber email field resolved to an empty or invalid value.',
            ]);
            $this->log->add(
                'warning',
                'submission',
                $submission_id,
                get_current_user_id(),
                sprintf('Mailchimp sync skipped for audience %s because the mapped subscriber email field was empty or invalid.', $audience_id),
                Contract::LOG_CATEGORY
            );
            return;
        }

        $consent_field_id = isset($effective['consent_field_id']) ? (string) $effective['consent_field_id'] : '';
        $consent_required = $consent_field_id !== '';
        $consent_granted = !$consent_required || $this->hasConsent($context['field_values'][$consent_field_id] ?? null);
        $subscriber_hash = md5(strtolower($email));
        $identity = $this->identity_store->buildIdentityRecord(
            $submission_id,
            $form_id,
            $author_user_id,
            $audience_id,
            $email,
            $subscriber_hash
        );
        if (!$consent_granted) {
            $this->persistSkippedState($submission_id, [
                'schema_version' => Contract::IDENTITY_SCHEMA_VERSION,
                'audience_id' => $audience_id,
                'subscription_status' => $subscription_status,
                'subscriber_email' => $email,
                'subscriber_hash' => $subscriber_hash,
                'email_field_id' => $email_field_id,
                'consent_field_id' => $consent_field_id,
                'consent_required' => true,
                'consent_granted' => false,
                'owner' => $owner_context,
                'identity' => $identity,
                'storage_strategy' => $this->buildStorageStrategy($owner_context),
                'reason' => 'consent_not_granted',
                'message' => 'Mailchimp sync skipped because the configured consent source was not accepted.',
            ]);
            $this->log->add(
                'info',
                'submission',
                $submission_id,
                get_current_user_id(),
                sprintf('Mailchimp sync skipped for %s because the configured consent source was not accepted.', $email),
                Contract::LOG_CATEGORY
            );
            return;
        }

        $tags = $this->normalizeTags($effective['static_tags'] ?? []);
        $merge_fields = $this->buildMergeFields($effective, $context);
        $base_state = [
            'schema_version' => Contract::IDENTITY_SCHEMA_VERSION,
            'enabled' => true,
            'status' => Contract::STATUS_PROCESSING,
            'audience_id' => $audience_id,
            'subscription_status' => $subscription_status,
            'subscriber_email' => $email,
            'subscriber_hash' => $subscriber_hash,
            'email_field_id' => $email_field_id,
            'consent_field_id' => $consent_field_id,
            'consent_required' => $consent_required,
            'consent_granted' => $consent_granted,
            'owner' => $owner_context,
            'identity' => $identity,
            'storage_strategy' => $this->buildStorageStrategy($owner_context),
            'tags' => $tags,
            'last_error' => '',
            'last_attempted_at' => gmdate('c'),
        ];
        $this->state_store->mergeState($submission_id, $base_state);

        $existing_member = $this->client->getMember($api_key, $audience_id, $subscriber_hash);
        if (is_wp_error($existing_member)) {
            $status_code = (int) ($existing_member->get_error_data()['status'] ?? 0);
            if ($status_code !== 404) {
                $this->handleApiFailure($submission_id, $audience_id, $email, $existing_member);
                return;
            }
        }

        $operation = 'created';
        $member_status = $subscription_status;
        $preserved_status = false;

        if (is_wp_error($existing_member) && (int) ($existing_member->get_error_data()['status'] ?? 0) === 404) {
            $upsert = $this->client->upsertMember($api_key, $audience_id, $subscriber_hash, [
                'email_address' => $email,
                'status_if_new' => $subscription_status,
                'status' => $subscription_status,
                'merge_fields' => $merge_fields,
            ]);

            if (is_wp_error($upsert)) {
                $this->handleApiFailure($submission_id, $audience_id, $email, $upsert);
                return;
            }

            $member_status = sanitize_key((string) ($upsert['body']['status'] ?? $subscription_status));
        } else {
            $operation = 'updated';
            $existing_status = sanitize_key((string) ($existing_member['body']['status'] ?? ''));
            $member_status = $existing_status !== '' ? $existing_status : $subscription_status;
            $update_payload = [
                'email_address' => $email,
                'merge_fields' => $merge_fields,
            ];

            if (in_array($existing_status, ['subscribed', 'pending'], true)) {
                $update_payload['status'] = $subscription_status;
            } elseif (in_array($existing_status, ['unsubscribed', 'cleaned', 'archived'], true)) {
                $preserved_status = true;
            }

            $update = $this->client->updateMember($api_key, $audience_id, $subscriber_hash, $update_payload);
            if (is_wp_error($update)) {
                $this->handleApiFailure($submission_id, $audience_id, $email, $update);
                return;
            }

            $member_status = sanitize_key((string) ($update['body']['status'] ?? $member_status));
        }

        if (!empty($tags)) {
            $tag_result = $this->client->updateMemberTags($api_key, $audience_id, $subscriber_hash, $tags);
            if (is_wp_error($tag_result)) {
                $this->handleApiFailure($submission_id, $audience_id, $email, $tag_result);
                return;
            }
        }

        $final_state = array_replace($base_state, [
            'status' => Contract::STATUS_SYNCED,
            'operation' => $operation,
            'member_status' => $member_status,
            'status_preserved' => $preserved_status,
            'last_synced_at' => gmdate('c'),
            'last_error' => '',
        ]);
        $this->state_store->saveState($submission_id, $final_state);
        $this->persistUserIdentityIndex($author_user_id, $identity, $final_state);

        $message = $preserved_status
            ? sprintf(
                'Mailchimp sync updated %s in audience %s while preserving the existing %s member status.',
                $email,
                $audience_id,
                $member_status !== '' ? $member_status : 'existing'
            )
            : sprintf(
                'Mailchimp sync %s %s in audience %s with status %s.',
                $operation,
                $email,
                $audience_id,
                $member_status !== '' ? $member_status : $subscription_status
            );

        $this->log->add(
            $preserved_status ? 'warning' : 'success',
            'submission',
            $submission_id,
            get_current_user_id(),
            $message,
            Contract::LOG_CATEGORY
        );
    }

    private function extractEmailValue($value): string
    {
        $string = $this->extractFirstString($value);
        return sanitize_email($string);
    }

    private function extractFirstString($value): string
    {
        if (is_array($value)) {
            foreach ($value as $nested) {
                $resolved = $this->extractFirstString($nested);
                if ($resolved !== '') {
                    return $resolved;
                }
            }

            return '';
        }

        if (is_scalar($value)) {
            $string = trim((string) $value);
            if ($string === '') {
                return '';
            }

            if ($string[0] === '[' || $string[0] === '{') {
                $decoded = json_decode($string, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    return $this->extractFirstString($decoded);
                }
            }

            return sanitize_text_field($string);
        }

        return '';
    }

    private function hasConsent($value): bool
    {
        if (is_array($value)) {
            foreach ($value as $nested) {
                if ($this->hasConsent($nested)) {
                    return true;
                }
            }

            return false;
        }

        if (is_bool($value)) {
            return $value;
        }

        if (is_numeric($value)) {
            return (int) $value === 1;
        }

        $string = trim((string) $value);
        if ($string === '') {
            return false;
        }

        if ($string[0] === '[' || $string[0] === '{') {
            $decoded = json_decode($string, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $this->hasConsent($decoded);
            }
        }

        return in_array(strtolower($string), ['1', 'true', 'yes', 'on', 'accepted'], true);
    }

    private function buildMergeFields(array $effective, array $context): array
    {
        $merge_fields = [];

        $first_name_field = isset($effective['first_name_field']) ? (string) $effective['first_name_field'] : '';
        $first_name = $this->extractFirstString($context['field_values'][$first_name_field] ?? null);
        if ($first_name !== '') {
            $merge_fields['FNAME'] = $first_name;
        }

        $last_name_field = isset($effective['last_name_field']) ? (string) $effective['last_name_field'] : '';
        $last_name = $this->extractFirstString($context['field_values'][$last_name_field] ?? null);
        if ($last_name !== '') {
            $merge_fields['LNAME'] = $last_name;
        }

        return $merge_fields;
    }

    private function normalizeTags($tags): array
    {
        if (!is_array($tags)) {
            return [];
        }

        $normalized = [];
        foreach ($tags as $tag) {
            $clean = sanitize_text_field((string) $tag);
            if ($clean !== '') {
                $normalized[] = $clean;
            }
        }

        return array_values(array_unique($normalized));
    }

    private function handleApiFailure(int $submission_id, string $audience_id, string $email, WP_Error $error): void
    {
        $status_code = (int) ($error->get_error_data()['status'] ?? 0);
        $detail = $this->formatErrorForLog($error);
        $current_state = $this->state_store->getState($submission_id);

        $this->persistFailureState($submission_id, [
            'schema_version' => Contract::IDENTITY_SCHEMA_VERSION,
            'audience_id' => $audience_id,
            'subscriber_email' => $email,
            'owner' => isset($current_state['owner']) && is_array($current_state['owner']) ? $current_state['owner'] : [],
            'identity' => isset($current_state['identity']) && is_array($current_state['identity']) ? $current_state['identity'] : [],
            'storage_strategy' => isset($current_state['storage_strategy']) && is_array($current_state['storage_strategy']) ? $current_state['storage_strategy'] : [],
            'last_error' => $detail,
        ]);

        $this->log->add(
            'error',
            'submission',
            $submission_id,
            get_current_user_id(),
            sprintf('Mailchimp sync failed for %s in audience %s: %s', $email, $audience_id, $detail),
            Contract::LOG_CATEGORY
        );

        if ($status_code === 0 || $status_code === 429 || $status_code >= 500) {
            throw new \RuntimeException('Mailchimp sync transient failure: ' . $detail);
        }
    }

    private function persistUserIdentityIndex(int $user_id, array $identity, array $state): void
    {
        if ($user_id <= 0 || empty($identity)) {
            return;
        }

        $this->identity_store->persistUserIdentity($user_id, $identity, $state);
    }

    private function buildStorageStrategy(array $owner_context): array
    {
        return [
            'canonical' => 'submission_meta',
            'user_indexed' => !empty($owner_context['wordpress_user_id']),
            'dedicated_record' => false,
        ];
    }

    private function persistSkippedState(int $submission_id, array $state): void
    {
        $this->state_store->mergeState($submission_id, array_replace([
            'enabled' => true,
            'status' => Contract::STATUS_SKIPPED,
            'last_error' => '',
            'last_attempted_at' => gmdate('c'),
        ], $state));
    }

    private function persistFailureState(int $submission_id, array $state): void
    {
        $this->state_store->mergeState($submission_id, array_replace([
            'enabled' => true,
            'status' => Contract::STATUS_FAILED,
            'last_attempted_at' => gmdate('c'),
        ], $state));
    }

    private function formatErrorForLog(WP_Error $error): string
    {
        $status = (int) ($error->get_error_data()['status'] ?? 0);
        $message = sanitize_text_field($error->get_error_message());

        if ($status > 0) {
            return sprintf('HTTP %d: %s', $status, $message);
        }

        return $message;
    }
}
