<?php

namespace GriffinForms\Includes\Pipelines\Accounts;

use GriffinForms\Config;
use GriffinForms\Includes\Mailer\MailHandler;
use GriffinForms\Includes\Pipelines\SubmissionContextBuilder;
use GriffinForms\Log;

class LifecycleExecutor
{
    protected $log;
    protected $state_store;
    protected $password_vault;
    protected $submission_form_id_cache = [];

    public function __construct()
    {
        $this->log = Log::getInstance();
        $this->state_store = new ActionStateStore();
        $this->password_vault = new PasswordVault();
    }

    public function executeImmediateAction(int $submission_id, string $action_id, array $payload, string $origin = 'job')
    {
        if ($submission_id <= 0 || $action_id === '') {
            return new \WP_Error('invalid_account_action_context', __('Invalid account action context.', 'griffinforms-form-builder'));
        }

        $target_email = sanitize_email((string) ($payload['target_email'] ?? ''));
        if ($target_email === '') {
            return $this->markFailureAndReturn(
                $submission_id,
                $action_id,
                'missing_target_email',
                'Queued account action is missing a target email.'
            );
        }

        $role = sanitize_key((string) ($payload['role'] ?? 'subscriber'));
        if ($role === '' || !get_role($role)) {
            return $this->markFailureAndReturn(
                $submission_id,
                $action_id,
                'invalid_role',
                'The account action references a user role that is not available.'
            );
        }

        $existing_user = get_user_by('email', $target_email);
        $existing_behavior = sanitize_text_field((string) ($payload['existing_account_behavior'] ?? 'error'));

        if ($existing_user instanceof \WP_User) {
            return $this->processExistingUserBranch($submission_id, $action_id, $payload, $existing_user, $existing_behavior, $origin);
        }

        return $this->processCreateBranch($submission_id, $action_id, $payload, $origin);
    }

    public function sendConfirmationNow(int $submission_id, string $action_id, array $payload)
    {
        if ($submission_id <= 0 || $action_id === '') {
            return new \WP_Error('invalid_confirmation_context', __('Invalid confirmation-email context.', 'griffinforms-form-builder'));
        }

        $recipient = sanitize_email((string) ($payload['target_email'] ?? ''));
        if ($recipient === '') {
            return new \WP_Error('missing_confirmation_recipient', __('The selected account action does not have a valid target email.', 'griffinforms-form-builder'));
        }

        $message_id = isset($payload['confirmation_message_id']) ? (int) ($payload['confirmation_message_id']) : 0;
        if ($message_id <= 0) {
            return new \WP_Error('missing_confirmation_message', __('No confirmation message is available for this account action.', 'griffinforms-form-builder'));
        }

        $result = $this->deliverConfirmationEmail($submission_id, $payload, $recipient, isset($payload['user_id']) ? (int) $payload['user_id'] : 0);
        if (is_wp_error($result)) {
            $this->state_store->updateAction($submission_id, $action_id, [
                'confirmation_email_status' => 'failed',
                'confirmation_email_reason' => sanitize_key($result->get_error_code()),
                'confirmation_email_sent_at' => '',
            ]);
            return $result;
        }

        $this->state_store->updateAction($submission_id, $action_id, [
            'confirmation_email_status' => 'sent',
            'confirmation_email_reason' => 'sent_on_admin_request',
            'confirmation_email_sent_at' => gmdate('c'),
        ]);

        $this->logInfo($submission_id, sprintf('Account confirmation email sent to %s on admin request.', $recipient));
        return true;
    }

    private function processExistingUserBranch(
        int $submission_id,
        string $action_id,
        array $payload,
        \WP_User $existing_user,
        string $existing_behavior,
        string $origin
    ) {
        switch ($existing_behavior) {
            case 'accept_noop':
                $password_setup_changes = $this->getExistingAccountPasswordSetupState($payload, 'existing_account_noop');
                $this->state_store->updateAction($submission_id, $action_id, [
                    'status' => Contract::STATUS_COMPLETED,
                    'status_reason' => $origin === 'admin' ? 'accepted_existing_user_noop_admin' : 'accepted_existing_user_noop',
                    'status_message' => 'An account with this email already exists. No account changes were made.',
                    'user_id' => (int) $existing_user->ID,
                    'result' => 'accepted_noop',
                    'password_setup_email_status' => $password_setup_changes['status'],
                    'password_setup_email_reason' => $password_setup_changes['reason'],
                    'password_setup_email_sent_at' => '',
                    'confirmation_email_status' => !empty($payload['send_confirmation_email']) ? 'skipped' : 'not_requested',
                    'confirmation_email_reason' => !empty($payload['send_confirmation_email']) ? 'existing_account_noop' : '',
                ]);
                $this->logInfo($submission_id, sprintf('Account action accepted existing user without changes for %s.', $existing_user->user_email));
                return true;

            case 'update_user':
                return $this->updateExistingUser($submission_id, $action_id, $payload, $existing_user);

            case 'error':
            default:
                return $this->markFailureAndReturn(
                    $submission_id,
                    $action_id,
                    'existing_user_found',
                    'An account with this email already exists and the setup is configured to stop with an error.'
                );
        }
    }

    private function updateExistingUser(int $submission_id, string $action_id, array $payload, \WP_User $existing_user)
    {
        $args = [
            'ID' => (int) $existing_user->ID,
            'first_name' => sanitize_text_field((string) ($payload['resolved_first_name'] ?? '')),
            'last_name' => sanitize_text_field((string) ($payload['resolved_last_name'] ?? '')),
            'role' => sanitize_key((string) ($payload['role'] ?? 'subscriber')),
        ];

        $password_result = $this->resolveUserPassForExecution($submission_id, $action_id, $payload);
        if (is_wp_error($password_result)) {
            return $password_result;
        }

        if ($password_result !== '') {
            $args['user_pass'] = $password_result;
        }

        $updated = wp_update_user($args);
        if (is_wp_error($updated)) {
            return $this->markFailureAndReturn(
                $submission_id,
                $action_id,
                'wp_update_user_failed',
                $updated->get_error_message()
            );
        }

        $this->storeProfileImageMeta((int) $existing_user->ID, $payload);
        $this->storeUpdatedUserOriginMeta((int) $existing_user->ID, $submission_id, $action_id);

        $runtime_issues = isset($payload['runtime_issues']) && is_array($payload['runtime_issues']) ? $payload['runtime_issues'] : [];
        if (!empty($payload['resolved_username']) && (string) ($payload['resolved_username'] ?? '') !== (string) $existing_user->user_login) {
            $runtime_issues[] = 'username_not_changed_on_existing_user_update';
        }

        $this->state_store->updateAction($submission_id, $action_id, [
            'status' => Contract::STATUS_COMPLETED,
            'status_reason' => 'existing_user_updated',
            'status_message' => 'Existing user updated successfully.',
            'user_id' => (int) $existing_user->ID,
            'result' => 'updated',
            'runtime_issues' => array_values(array_unique($runtime_issues)),
            'password_setup_email_status' => (string) ($payload['password_mode'] ?? '') === 'mapped_password' ? 'not_requested' : 'skipped',
            'password_setup_email_reason' => (string) ($payload['password_mode'] ?? '') === 'mapped_password' ? '' : 'existing_account_updated',
            'password_setup_email_sent_at' => '',
            'confirmation_email_status' => !empty($payload['send_confirmation_email']) ? 'skipped' : 'not_requested',
            'confirmation_email_reason' => !empty($payload['send_confirmation_email']) ? 'existing_account_updated' : '',
        ]);

        $this->logInfo($submission_id, sprintf('Existing WordPress user updated for %s.', $existing_user->user_email));
        return true;
    }

    private function processCreateBranch(int $submission_id, string $action_id, array $payload, string $origin)
    {
        $target_email = sanitize_email((string) ($payload['target_email'] ?? ''));
        $username = sanitize_user((string) ($payload['resolved_username'] ?? ''), true);
        if ($username === '') {
            return $this->markFailureAndReturn(
                $submission_id,
                $action_id,
                'missing_username',
                'A username could not be resolved for account creation.'
            );
        }

        if (username_exists($username)) {
            return $this->markFailureAndReturn(
                $submission_id,
                $action_id,
                'username_collision',
                'The resolved username already exists.'
            );
        }

        $password_result = $this->resolveUserPassForExecution($submission_id, $action_id, $payload);
        if (is_wp_error($password_result)) {
            return $password_result;
        }

        $user_id = wp_insert_user([
            'user_login' => $username,
            'user_email' => $target_email,
            'user_pass' => $password_result,
            'role' => sanitize_key((string) ($payload['role'] ?? 'subscriber')),
            'first_name' => sanitize_text_field((string) ($payload['resolved_first_name'] ?? '')),
            'last_name' => sanitize_text_field((string) ($payload['resolved_last_name'] ?? '')),
        ]);

        if (is_wp_error($user_id)) {
            return $this->markFailureAndReturn(
                $submission_id,
                $action_id,
                'wp_insert_user_failed',
                $user_id->get_error_message()
            );
        }

        $user_id = (int) $user_id;
        $this->storeProfileImageMeta($user_id, $payload);
        $this->storeCreatedUserOriginMeta($user_id, $submission_id, $action_id);

        $status_reason = 'user_created';
        $status_message = 'WordPress user created successfully.';
        if ($origin === 'admin_activate') {
            $status_reason = 'pending_account_activated';
            $status_message = 'Pending account was activated and created successfully.';
        } elseif ($origin === 'admin_create_now') {
            $status_reason = 'manual_account_created';
            $status_message = 'WordPress user created from a manual review action.';
        } elseif ($origin === 'admin_retry') {
            $status_reason = 'failed_action_retried_successfully';
            $status_message = 'Previously failed account action completed successfully on retry.';
        }

        $this->state_store->updateAction($submission_id, $action_id, [
            'status' => Contract::STATUS_COMPLETED,
            'status_reason' => $status_reason,
            'status_message' => $status_message,
            'user_id' => $user_id,
            'result' => 'created',
        ]);

        $this->logInfo($submission_id, sprintf('WordPress user created for %s.', $target_email));
        $confirmation_result = $this->deliverPostCreateEmails($submission_id, $action_id, $payload, $target_email, $user_id);
        if (is_wp_error($confirmation_result)) {
            return $confirmation_result;
        }

        return $confirmation_result;
    }

    private function deliverPostCreateEmails(int $submission_id, string $action_id, array $payload, string $recipient, int $user_id)
    {
        $native_password_notice = null;

        if ((string) ($payload['password_mode'] ?? 'generate_password') !== 'mapped_password') {
            $password_setup_result = $this->deliverNativePasswordSetupEmailAfterCreate($submission_id, $action_id, $recipient, $user_id);
            if (is_wp_error($password_setup_result)) {
                $native_password_notice = [
                    'message' => __('The account was created, but the native WordPress password setup email could not be sent.', 'griffinforms-form-builder'),
                    'notice_variant' => 'warning',
                ];
            }
        }

        $confirmation_result = $this->deliverConfirmationEmailAfterCreate($submission_id, $action_id, $payload, $recipient, $user_id);
        if (is_wp_error($confirmation_result)) {
            return $confirmation_result;
        }

        if (is_array($confirmation_result)) {
            return $confirmation_result;
        }

        return $native_password_notice ?: true;
    }

    private function deliverNativePasswordSetupEmailAfterCreate(int $submission_id, string $action_id, string $recipient, int $user_id)
    {
        $result = $this->deliverNativePasswordSetupEmail($user_id, $recipient);
        if (is_wp_error($result)) {
            $this->state_store->updateAction($submission_id, $action_id, [
                'password_setup_email_status' => 'failed',
                'password_setup_email_reason' => sanitize_key($result->get_error_code()),
                'password_setup_email_sent_at' => '',
            ]);
            $this->logInfo($submission_id, sprintf('Native WordPress password setup email failed for %s: %s', $recipient, $result->get_error_message()), 'warning');
            return $result;
        }

        $this->state_store->updateAction($submission_id, $action_id, [
            'password_setup_email_status' => 'sent',
            'password_setup_email_reason' => 'sent_after_account_creation',
            'password_setup_email_sent_at' => gmdate('c'),
        ]);
        $this->logInfo($submission_id, sprintf('Native WordPress password setup email sent to %s after account creation.', $recipient));
        return true;
    }

    private function deliverConfirmationEmailAfterCreate(int $submission_id, string $action_id, array $payload, string $recipient, int $user_id)
    {
        if (empty($payload['send_confirmation_email'])) {
            $this->state_store->updateAction($submission_id, $action_id, [
                'confirmation_email_status' => 'not_requested',
                'confirmation_email_reason' => '',
                'confirmation_email_sent_at' => '',
            ]);
            return true;
        }

        $result = $this->deliverConfirmationEmail($submission_id, $payload, $recipient, $user_id);
        if (is_wp_error($result)) {
            $this->state_store->updateAction($submission_id, $action_id, [
                'confirmation_email_status' => 'failed',
                'confirmation_email_reason' => sanitize_key($result->get_error_code()),
                'confirmation_email_sent_at' => '',
            ]);
            return $result;
        }

        $this->state_store->updateAction($submission_id, $action_id, [
            'confirmation_email_status' => 'sent',
            'confirmation_email_reason' => 'sent_after_account_creation',
            'confirmation_email_sent_at' => gmdate('c'),
        ]);
        $this->logInfo($submission_id, sprintf('Account confirmation email sent to %s after account creation.', $recipient));
        return true;
    }

    private function deliverConfirmationEmail(int $submission_id, array $payload, string $recipient, int $user_id)
    {
        $message_id = isset($payload['confirmation_message_id']) ? (int) ($payload['confirmation_message_id']) : 0;
        if ($message_id <= 0) {
            $this->logInfo($submission_id, 'Account confirmation email could not be sent because no confirmation message was selected.', 'warning');
            return new \WP_Error('missing_confirmation_message', __('No confirmation message is available for this account action.', 'griffinforms-form-builder'));
        }

        $message = $this->getMessageById($message_id);
        if (!$message) {
            $this->logInfo($submission_id, sprintf('Account confirmation message %d no longer exists; email not sent.', $message_id), 'warning');
            return new \WP_Error('confirmation_message_missing', __('The selected confirmation message no longer exists.', 'griffinforms-form-builder'));
        }

        $context = SubmissionContextBuilder::getInstance()->build($submission_id);
        $form = $this->loadForm(isset($context['form_id']) ? (int) $context['form_id'] : 0);
        $context['form_object'] = $form;
        $context['account_action'] = [
            'user_id' => $user_id,
            'email' => $recipient,
            'username' => (string) ($payload['resolved_username'] ?? ''),
        ];

        $merge_engine = new \GriffinForms\Includes\MergeTokens\Engine();
        $subject = $merge_engine->render($message['name'] ?? '', $context);
        $content = $merge_engine->render($message['content'] ?? '', $context);
        if (trim($content) === '') {
            $this->logInfo($submission_id, sprintf('Account confirmation message %d has empty content; email not sent.', $message_id), 'warning');
            return new \WP_Error('empty_confirmation_message', __('The selected confirmation message is empty.', 'griffinforms-form-builder'));
        }

        $mailer = $this->createMailer($submission_id);
        $mailer->setEmailSubject($subject);
        $mailer->setEmailContent($content);
        $mailer->setEmailRecipient($recipient);
        $send_result = $mailer->sendMail();

        if (is_wp_error($send_result)) {
            $this->logInfo($submission_id, sprintf('Account confirmation email failed for %s: %s', $recipient, $send_result->get_error_message()), 'warning');
            return new \WP_Error('mail_send_failed', $send_result->get_error_message());
        }

        return true;
    }

    private function resolveUserPassForExecution(int $submission_id, string $action_id, array $payload)
    {
        $password_mode = (string) ($payload['password_mode'] ?? 'generate_password');
        if ($password_mode !== 'mapped_password') {
            return wp_generate_password(24, true, true);
        }

        $stored_password = $this->password_vault->retrieveForAction($action_id);
        $this->password_vault->deleteForAction($action_id);

        if ($stored_password === '') {
            return $this->markFailureAndReturn(
                $submission_id,
                $action_id,
                'mapped_password_secret_missing',
                'The mapped password is no longer available for execution.'
            );
        }

        return $stored_password;
    }

    private function deliverNativePasswordSetupEmail(int $user_id, string $recipient)
    {
        if ($user_id <= 0 || $recipient === '') {
            return new \WP_Error('invalid_password_setup_context', __('Invalid password setup email context.', 'griffinforms-form-builder'));
        }

        $mail_failed = null;
        $mail_succeeded = false;

        $failed_handler = function ($error) use (&$mail_failed, $recipient) {
            if (!is_wp_error($error)) {
                return;
            }

            $mail_data = $error->get_error_data();
            $to = isset($mail_data['to']) ? (array) $mail_data['to'] : [];
            $normalized_to = array_map('sanitize_email', $to);
            if (in_array(sanitize_email($recipient), $normalized_to, true)) {
                $mail_failed = $error;
            }
        };

        $succeeded_handler = function ($mail_data) use (&$mail_succeeded, $recipient) {
            $to = isset($mail_data['to']) ? (array) $mail_data['to'] : [];
            $normalized_to = array_map('sanitize_email', $to);
            if (in_array(sanitize_email($recipient), $normalized_to, true)) {
                $mail_succeeded = true;
            }
        };

        $suppress_admin_handler = function ($send, $user) use ($user_id) {
            if ($user instanceof \WP_User && (int) $user->ID === $user_id) {
                return false;
            }

            return $send;
        };

        add_action('wp_mail_failed', $failed_handler, 10, 1);
        add_action('wp_mail_succeeded', $succeeded_handler, 10, 1);
        add_filter('wp_send_new_user_notification_to_admin', $suppress_admin_handler, 10, 2);

        try {
            wp_new_user_notification($user_id, null, 'user');
        } finally {
            remove_action('wp_mail_failed', $failed_handler, 10);
            remove_action('wp_mail_succeeded', $succeeded_handler, 10);
            remove_filter('wp_send_new_user_notification_to_admin', $suppress_admin_handler, 10);
        }

        if ($mail_failed instanceof \WP_Error) {
            return new \WP_Error('mail_send_failed', $mail_failed->get_error_message());
        }

        if ($mail_succeeded) {
            return true;
        }

        return new \WP_Error(
            'native_password_setup_email_not_sent',
            __('WordPress did not confirm delivery of the native password setup email.', 'griffinforms-form-builder')
        );
    }

    private function getExistingAccountPasswordSetupState(array $payload, string $reason): array
    {
        if ((string) ($payload['password_mode'] ?? '') === 'mapped_password') {
            return [
                'status' => 'not_requested',
                'reason' => '',
            ];
        }

        return [
            'status' => 'skipped',
            'reason' => sanitize_key($reason),
        ];
    }

    private function storeProfileImageMeta(int $user_id, array $payload): void
    {
        if ($user_id <= 0) {
            return;
        }

        $profile_image = isset($payload['resolved_profile_image']) && is_array($payload['resolved_profile_image'])
            ? $payload['resolved_profile_image']
            : [];
        if (empty($profile_image)) {
            return;
        }

        $attachment_id = absint($profile_image['attachment_id'] ?? 0);
        $url = esc_url_raw((string) ($profile_image['url'] ?? ''));

        if ($attachment_id > 0) {
            update_user_meta($user_id, Contract::PROFILE_IMAGE_ATTACHMENT_META_KEY, $attachment_id);
        }

        if ($url !== '') {
            update_user_meta($user_id, Contract::PROFILE_IMAGE_URL_META_KEY, $url);
        }
    }

    private function markFailureAndReturn(int $submission_id, string $action_id, string $reason, string $message)
    {
        $existing_action = $this->state_store->getAction($submission_id, $action_id);
        $password_mode = (string) ($existing_action['password_mode'] ?? '');
        $this->state_store->updateAction($submission_id, $action_id, [
            'status' => Contract::STATUS_FAILED,
            'status_reason' => sanitize_key($reason),
            'status_message' => sanitize_text_field($message),
            'password_setup_email_status' => $password_mode !== 'mapped_password' ? 'blocked' : 'not_requested',
            'password_setup_email_reason' => $password_mode !== 'mapped_password' ? sanitize_key($reason) : '',
            'confirmation_email_status' => 'blocked',
            'confirmation_email_reason' => sanitize_key($reason),
        ]);

        $this->logInfo($submission_id, sprintf('WordPress account action failed: %s', $message), 'error');
        return new \WP_Error(sanitize_key($reason), __($message, 'griffinforms-form-builder'));
    }

    private function createMailer(int $submission_id): MailHandler
    {
        $mailer = new MailHandler();
        $mailer->setLogContext('submission', $submission_id);
        $mailer->setSubmissionId($submission_id);
        return $mailer;
    }

    private function loadForm(int $form_id)
    {
        if ($form_id <= 0 || !class_exists('\GriffinForms\Items\Form')) {
            return null;
        }

        $form = new \GriffinForms\Items\Form();
        $loaded = $form->loadItem($form_id);
        return $loaded ? $form : null;
    }

    private function getMessageById(int $message_id): ?array
    {
        global $wpdb;
        $table = Config::getInstance()->getTable('message');
        if (!$table) {
            return null;
        }

        $row = $wpdb->get_row(
            $wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $message_id),
            ARRAY_A
        );

        return is_array($row) ? $row : null;
    }

    private function logInfo(int $submission_id, string $message, string $level = 'info'): void
    {
        if (!$this->log) {
            return;
        }

        $this->log->add(
            $level,
            'submission',
            $submission_id,
            get_current_user_id(),
            $message,
            Contract::LOG_CATEGORY
        );
    }

    private function storeCreatedUserOriginMeta(int $user_id, int $submission_id, string $action_id): void
    {
        if ($user_id <= 0 || $submission_id <= 0 || $action_id === '') {
            return;
        }

        $form_id = $this->getFormIdForSubmission($submission_id);
        if ($form_id <= 0) {
            return;
        }

        update_user_meta($user_id, Contract::USER_META_ORIGIN_FORM_ID, $form_id);
        update_user_meta($user_id, Contract::USER_META_ORIGIN_SUBMISSION_ID, $submission_id);
        update_user_meta($user_id, Contract::USER_META_ORIGIN_ACTION_ID, sanitize_key($action_id));
    }

    private function storeUpdatedUserOriginMeta(int $user_id, int $submission_id, string $action_id): void
    {
        if ($user_id <= 0 || $submission_id <= 0 || $action_id === '') {
            return;
        }

        $form_id = $this->getFormIdForSubmission($submission_id);
        if ($form_id <= 0) {
            return;
        }

        update_user_meta($user_id, Contract::USER_META_LAST_FORM_ID, $form_id);
        update_user_meta($user_id, Contract::USER_META_LAST_SUBMISSION_ID, $submission_id);
        update_user_meta($user_id, Contract::USER_META_LAST_ACTION_ID, sanitize_key($action_id));
    }

    private function getFormIdForSubmission(int $submission_id): int
    {
        if ($submission_id <= 0) {
            return 0;
        }

        if (isset($this->submission_form_id_cache[$submission_id])) {
            return (int) $this->submission_form_id_cache[$submission_id];
        }

        global $wpdb;
        $table = Config::getInstance()->getTable('submission');
        $form_id = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT form_id FROM %i WHERE id = %d LIMIT 1",
                $table,
                $submission_id
            )
        );

        $this->submission_form_id_cache[$submission_id] = $form_id;
        return $form_id;
    }
}
