<?php

namespace GriffinForms\Admin\Ajax;

class Submission extends \GriffinForms\Admin\Ajax\Format
{
    protected $accounts_admin_action_service;

    protected function setAccountsadminactionservice()
    {
        $this->accounts_admin_action_service = new \GriffinForms\Includes\Pipelines\Accounts\AdminActionService();
    }

    public function decryptPassword()
    {
        $this->checkNonce('griffinforms_decrypt_password');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to decrypt this value.', 'griffinforms-form-builder'));
        }

        $this->checkPostData(array('field_id', 'submission_id', 'iteration'));

        $field_id = absint($_POST['field_id']);
        $submission_id = absint($_POST['submission_id']);
        $iteration = absint($_POST['iteration']);

        if ($field_id <= 0 || $submission_id <= 0 || $iteration < 0) {
            $this->returnError(__('Invalid request parameters.', 'griffinforms-form-builder'));
        }

        $value = $this->locatePasswordValue($submission_id, $field_id, $iteration);
        if ($value === null) {
            $this->returnError(__('Password value not found.', 'griffinforms-form-builder'));
        }

        if (!is_array($value) || empty($value['encrypted'])) {
            $plain = is_array($value) && isset($value['value']) ? (string) $value['value'] : (string) $value;
            $this->returnSuccess(array('value' => $plain));
        }

        $plain = $this->decryptPasswordValue($field_id, $value);
        if ($plain === false) {
            $this->returnError(__('Unable to decrypt the password with the provided key.', 'griffinforms-form-builder'));
        }

        $this->returnSuccess(array('value' => $plain));
    }

    private function locatePasswordValue($submission_id, $field_id, $iteration)
    {
        global $wpdb;
        $table = $this->config->getTable('submission');
        $row = $wpdb->get_row(
            $wpdb->prepare("SELECT submission FROM {$table} WHERE id = %d LIMIT 1", $submission_id),
            ARRAY_A
        );

        if (!$row || !isset($row['submission'])) {
            return null;
        }

        $submission = maybe_unserialize($row['submission']);
        if (!is_array($submission)) {
            return null;
        }

        foreach ($submission as $page_fields) {
            if (!is_array($page_fields)) {
                continue;
            }

            $keys = array(
                $field_id,
                (string) $field_id,
                'field_' . $field_id
            );

            $field_values = null;
            foreach ($keys as $key) {
                if (isset($page_fields[$key])) {
                    $field_values = $page_fields[$key];
                    break;
                }
            }

            if ($field_values === null) {
                continue;
            }

            if (!is_array($field_values)) {
                $field_values = array(0 => $field_values);
            }

            return $field_values[$iteration] ?? null;
        }

        return null;
    }

    private function decryptPasswordValue($field_id, array $stored_value)
    {
        if (empty($stored_value['ciphertext']) || empty($stored_value['iv'])) {
            return false;
        }

        $field = new \GriffinForms\Items\Field();
        $field->loadItem($field_id);
        $settings = $field->getProp('field_settings');
        $settings = is_array($settings) ? $settings : array();

        if (empty($settings['encryption_key'])) {
            return false;
        }

        $key = $this->deriveEncryptionKey($settings['encryption_key']);
        if (strlen($key) !== 32) {
            $this->log->add('error', 'submission', $field_id, get_current_user_id(), 'Invalid encryption key length for password field.', 'security');
            return false;
        }

        $iv = base64_decode($stored_value['iv'], true);
        $ciphertext = base64_decode($stored_value['ciphertext'], true);

        if ($iv === false || $ciphertext === false) {
            return false;
        }

        $method = isset($stored_value['method']) ? $stored_value['method'] : 'AES-256-CBC';
        $plain = openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);

        if ($plain === false) {
            $this->log->add('error', 'submission', $field_id, get_current_user_id(), 'Failed to decrypt password field value.', 'security');
            return false;
        }

        return $plain;
    }

    private function deriveEncryptionKey($raw_key)
    {
        $raw_key = trim((string) $raw_key);
        if ($raw_key === '') {
            return '';
        }

        $decoded = base64_decode($raw_key, true);
        if ($decoded !== false && strlen($decoded) === 32) {
            return $decoded;
        }

        return hash('sha256', $raw_key, true);
    }

    public function __construct()
    {
        $this->setAll();
        add_action('wp_ajax_griffinforms_decrypt_password', array($this, 'decryptPassword'));
        add_action('wp_ajax_griffinforms_mark_payment_paid', array($this, 'markPaymentPaid'));
        add_action('wp_ajax_griffinforms_mark_payment_unpaid', array($this, 'markPaymentUnpaid'));
        add_action('wp_ajax_griffinforms_get_submission_logs', array($this, 'getSubmissionLogs'));
        add_action('wp_ajax_griffinforms_download_compliance_meta', array($this, 'downloadComplianceMeta'));
        add_action('wp_ajax_griffinforms_get_submission_accounts', array($this, 'getSubmissionAccounts'));
        add_action('wp_ajax_griffinforms_validate_submission_account_action', array($this, 'validateSubmissionAccountAction'));
        add_action('wp_ajax_griffinforms_execute_submission_account_action', array($this, 'executeSubmissionAccountAction'));
        add_action('wp_ajax_griffinforms_mark_submission_read', array($this, 'markSubmissionRead'));
        add_action('wp_ajax_griffinforms_mark_submissions_read_bulk', array($this, 'markSubmissionsReadBulk'));
    }

    public function downloadComplianceMeta()
    {
        $nonce = isset($_GET['nonce']) ? sanitize_text_field(wp_unslash($_GET['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'griffinforms_download_compliance_meta')) {
            $this->returnError(__('Invalid request.', 'griffinforms-form-builder'));
        }

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to download this data.', 'griffinforms-form-builder'));
        }

        $submission_id = isset($_GET['submission_id']) ? absint($_GET['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        global $wpdb;
        $table = $this->config->getTable('submission');
        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT submission_ip, submission_user_agent, submission_meta, form_id, form_layout_version, layout_snapshot_id FROM %i WHERE id = %d",
                $table,
                $submission_id
            ),
            ARRAY_A
        );
        if (!is_array($row)) {
            $this->returnError(__('Submission not found.', 'griffinforms-form-builder'));
        }

        $raw_meta = $row['submission_meta'] ?? '';
        $decoded = $this->maybeDecode($raw_meta);
        $events = [];
        $compliance = [];
        if (is_array($decoded)) {
            if (!empty($decoded['events']) && is_array($decoded['events'])) {
                $events = $decoded['events'];
            }
            if (!empty($decoded['compliance']) && is_array($decoded['compliance'])) {
                $compliance = $decoded['compliance'];
            }
        }

        $profile = isset($compliance['profile']) ? (string) $compliance['profile'] : '';
        $profile = strtolower($profile);
        $ip = isset($row['submission_ip']) ? (string) $row['submission_ip'] : '';
        $ua = isset($row['submission_user_agent']) ? (string) $row['submission_user_agent'] : '';

        if ($profile === 'gdpr') {
            if ($ip !== '') {
                $ip = $this->maskIp($ip);
            }
            if ($ua !== '') {
                $ua = $this->summarizeUserAgent($ua);
            }
            foreach ($events as &$event) {
                if (!is_array($event)) {
                    continue;
                }
                if (!empty($event['ip'])) {
                    $event['ip'] = $this->maskIp((string) $event['ip']);
                }
                if (!empty($event['user_agent'])) {
                    $event['user_agent'] = $this->summarizeUserAgent((string) $event['user_agent']);
                }
            }
            unset($event);
        }

        $payload = array(
            'submission_id' => $submission_id,
            'form_id' => isset($row['form_id']) ? (int) $row['form_id'] : 0,
            'layout_version' => isset($row['form_layout_version']) ? (int) $row['form_layout_version'] : 0,
            'layout_snapshot_id' => isset($row['layout_snapshot_id']) ? (int) $row['layout_snapshot_id'] : 0,
            'layout_snapshot' => null,
            'compliance' => $compliance,
            'ip' => $ip,
            'user_agent' => $ua,
            'events' => $events,
            'exported_at' => current_time('c', true),
            'redacted' => $profile === 'gdpr',
        );

        $snapshot_id = isset($row['layout_snapshot_id']) ? (int) $row['layout_snapshot_id'] : 0;
        $snapshot_table = $this->config->getTable('layout_snapshot');
        $snapshot_row = null;
        if ($snapshot_id > 0) {
            $snapshot_row = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT id, form_id, submission_id, layout_version, layout_hash, layout_json, created_at, created_by FROM %i WHERE id = %d",
                    $snapshot_table,
                    $snapshot_id
                ),
                ARRAY_A
            );
        }
        if (!is_array($snapshot_row)) {
            $snapshot_row = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT id, form_id, submission_id, layout_version, layout_hash, layout_json, created_at, created_by FROM %i WHERE submission_id = %d ORDER BY id DESC LIMIT 1",
                    $snapshot_table,
                    $submission_id
                ),
                ARRAY_A
            );
        }
        if (is_array($snapshot_row)) {
            if (!empty($snapshot_row['layout_json'])) {
                $decoded_snapshot = $this->maybeDecode($snapshot_row['layout_json']);
                $snapshot_row['layout_json'] = is_array($decoded_snapshot) ? $decoded_snapshot : $snapshot_row['layout_json'];
            }
            $payload['layout_snapshot'] = $snapshot_row;
        }

        $filename = 'submission-' . $submission_id . '-compliance-meta.json';
        nocache_headers();
        header('Content-Type: application/json; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        echo wp_json_encode($payload, JSON_PRETTY_PRINT);
        wp_die();
    }

    private function maskIp(string $ip): string
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            $parts = explode('.', $ip);
            if (count($parts) === 4) {
                $parts[3] = '***';
                return implode('.', $parts);
            }
            return $ip;
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            $packed = @inet_pton($ip);
            if ($packed === false) {
                return $ip;
            }
            $bytes = unpack('C*', $packed);
            if (!is_array($bytes)) {
                return $ip;
            }
            $total = count($bytes);
            for ($i = $total - 8 + 1; $i <= $total; $i++) {
                if (isset($bytes[$i])) {
                    $bytes[$i] = 0;
                }
            }
            $masked = pack('C*', ...array_values($bytes));
            $result = inet_ntop($masked);
            if ($result === false) {
                return $ip;
            }
            if (strpos($result, '::') !== false) {
                return preg_replace('/::.+$/', '::****', $result);
            }
            return $result . '::****';
        }

        return $ip;
    }

    private function summarizeUserAgent(string $ua): string
    {
        $ua = trim($ua);
        if ($ua === '') {
            return '';
        }

        $browser = '';
        $version = '';

        if (preg_match('/Edg\\/([\\d\\.]+)/i', $ua, $match)) {
            $browser = 'Edge';
            $version = $match[1];
        } elseif (preg_match('/Chrome\\/([\\d\\.]+)/i', $ua, $match) && stripos($ua, 'Edg/') === false) {
            $browser = 'Chrome';
            $version = $match[1];
        } elseif (preg_match('/Firefox\\/([\\d\\.]+)/i', $ua, $match)) {
            $browser = 'Firefox';
            $version = $match[1];
        } elseif (preg_match('/Version\\/([\\d\\.]+).*Safari/i', $ua, $match)) {
            $browser = 'Safari';
            $version = $match[1];
        }

        $os = '';
        if (preg_match('/Mac OS X ([\\d_\\.]+)/i', $ua, $match)) {
            $os = 'macOS ' . str_replace('_', '.', $match[1]);
        } elseif (preg_match('/Windows NT ([\\d\\.]+)/i', $ua, $match)) {
            $os = 'Windows ' . $match[1];
        } elseif (preg_match('/Android ([\\d\\.]+)/i', $ua, $match)) {
            $os = 'Android ' . $match[1];
        } elseif (preg_match('/iPhone OS ([\\d_]+)/i', $ua, $match)) {
            $os = 'iOS ' . str_replace('_', '.', $match[1]);
        } elseif (preg_match('/iPad; CPU OS ([\\d_]+)/i', $ua, $match)) {
            $os = 'iPadOS ' . str_replace('_', '.', $match[1]);
        }

        if ($browser === '' && $os === '') {
            return '';
        }

        $parts = [];
        if ($browser !== '') {
            $parts[] = trim($browser . ' ' . $version);
        }
        if ($os !== '') {
            $parts[] = $os;
        }

        return implode(' · ', $parts);
    }

    public function getSubmissionLogs()
    {
        $this->checkNonce('griffinforms_logs_timeline');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to view logs.', 'griffinforms-form-builder'));
        }

        $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        $offset = isset($_POST['offset']) ? absint($_POST['offset']) : 0;
        $limit = isset($_POST['limit']) ? absint($_POST['limit']) : 20;
        $include_jobs = isset($_POST['include_jobs']) && wp_unslash($_POST['include_jobs']) === '1';
        if ($limit <= 0) {
            $limit = 20;
        }
        if ($limit > 100) {
            $limit = 100;
        }

        $log = \GriffinForms\Log::getInstance();
        if (!$log->isLoggingEnabledPublic()) {
            $this->returnSuccess(array(
                'html' => '',
                'has_more' => false,
                'empty_message' => __('Logging is currently disabled in Settings.', 'griffinforms-form-builder'),
            ));
        }

        $fetch_limit = $limit + 1;
        $logs = $log->getByItemPaged('submission', $submission_id, $fetch_limit, $offset);
        $has_more = false;
        if (count($logs) > $limit) {
            $has_more = true;
            $logs = array_slice($logs, 0, $limit);
        }

        $timeline = new \GriffinForms\Admin\Html\Elements\LogsTimeline(
            'submission',
            $submission_id,
            array(
                'empty_message' => __('No logs recorded for this submission yet.', 'griffinforms-form-builder'),
                'include_jobs' => $include_jobs,
            )
        );
        $html = '';
        if ($include_jobs && $offset === 0) {
            $jobs = $timeline->getJobs();
            $html .= $timeline->renderJobEntries($jobs);
        }
        $html .= $timeline->renderEntries($logs);

        $this->returnSuccess(array(
            'html' => $html,
            'has_more' => $has_more,
        ));
    }

    public function getSubmissionAccounts()
    {
        $this->checkNonce('griffinforms_get_submission_accounts');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to view account actions for this submission.', 'griffinforms-form-builder'));
        }

        $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        $accounts = $this->accounts_admin_action_service->getSubmissionAccountsViewModel($submission_id);

        $this->returnSuccess(array(
            'accounts' => $accounts,
        ));
    }

    public function validateSubmissionAccountAction()
    {
        $this->checkNonce('griffinforms_validate_submission_account_action');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to manage account actions for this submission.', 'griffinforms-form-builder'));
        }

        $this->checkPostData(array('submission_id', 'action_id', 'account_action_type'));

        $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        $action_id = isset($_POST['action_id']) ? sanitize_key(wp_unslash($_POST['action_id'])) : '';
        $account_action_type = isset($_POST['account_action_type']) ? sanitize_key(wp_unslash($_POST['account_action_type'])) : '';
        $validation = $this->accounts_admin_action_service->validateActionRequest($submission_id, $action_id, $account_action_type);

        if (is_wp_error($validation)) {
            $this->returnError($validation->get_error_message());
        }

        $this->returnSuccess(array(
            'validated_action' => $validation,
            'execution_phase' => 'phase_2_endpoint_contract',
            'message' => __('Account action request validated. Execution is handled in a later phase.', 'griffinforms-form-builder'),
        ));
    }

    public function executeSubmissionAccountAction()
    {
        $this->checkNonce('griffinforms_execute_submission_account_action');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to manage account actions for this submission.', 'griffinforms-form-builder'));
        }

        $this->checkPostData(array('submission_id', 'action_id', 'account_action_type'));

        $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        $action_id = isset($_POST['action_id']) ? sanitize_key(wp_unslash($_POST['action_id'])) : '';
        $account_action_type = isset($_POST['account_action_type']) ? sanitize_key(wp_unslash($_POST['account_action_type'])) : '';

        $result = $this->accounts_admin_action_service->executeAction($submission_id, $action_id, $account_action_type);
        if (is_wp_error($result)) {
            $this->returnError($result->get_error_message());
        }

        $accounts = $this->accounts_admin_action_service->getSubmissionAccountsViewModel($submission_id);
        $message = __('Account action completed.', 'griffinforms-form-builder');
        $notice_variant = 'success';
        if (is_array($result)) {
            if (!empty($result['message'])) {
                $message = (string) $result['message'];
            }
            if (!empty($result['notice_variant'])) {
                $notice_variant = sanitize_key((string) $result['notice_variant']);
            }
        }
        $this->returnSuccess(array(
            'accounts' => $accounts,
            'message' => $message,
            'notice_variant' => $notice_variant,
        ));
    }

    public function markPaymentPaid()
    {
        $this->checkNonce('griffinforms_mark_payment_paid');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to perform this action.', 'griffinforms-form-builder'));
        }

        $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        $status_manager = \GriffinForms\Includes\Payments\SubmissionStatusManager::getInstance();
        $updated = $status_manager->markPaid($submission_id, array('gateway' => 'manual'));

        if (!$updated) {
            $this->returnError(__('Unable to update payment status.', 'griffinforms-form-builder'));
        }

        $current_user = wp_get_current_user();
        $user_label = $current_user && isset($current_user->user_login) ? $current_user->user_login : __('Unknown user', 'griffinforms-form-builder');
        $timestamp = wp_date(get_option('date_format') . ' ' . get_option('time_format'));
        $this->log->add(
            'info',
            'submission',
            $submission_id,
            get_current_user_id(),
            sprintf(__('Payment manually marked as paid by %1$s at %2$s.', 'griffinforms-form-builder'), $user_label, $timestamp),
            'Payment Manual'
        );

        $this->returnSuccess(array('message' => __('Payment marked as paid.', 'griffinforms-form-builder')));
    }

    public function markPaymentUnpaid()
    {
        $this->checkNonce('griffinforms_mark_payment_unpaid');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to perform this action.', 'griffinforms-form-builder'));
        }

        $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        $status_manager = \GriffinForms\Includes\Payments\SubmissionStatusManager::getInstance();
        $updated = $status_manager->updateStatus($submission_id, 'pending', array('gateway' => 'manual_unpaid'));

        if (!$updated) {
            $this->returnError(__('Unable to update payment status.', 'griffinforms-form-builder'));
        }

        $current_user = wp_get_current_user();
        $user_label = $current_user && isset($current_user->user_login) ? $current_user->user_login : __('Unknown user', 'griffinforms-form-builder');
        $timestamp = wp_date(get_option('date_format') . ' ' . get_option('time_format'));
        $this->log->add(
            'info',
            'submission',
            $submission_id,
            get_current_user_id(),
            sprintf(__('Payment manually marked as unpaid by %1$s at %2$s.', 'griffinforms-form-builder'), $user_label, $timestamp),
            'Payment Manual'
        );

        $this->returnSuccess(array('message' => __('Payment marked as unpaid.', 'griffinforms-form-builder')));
    }

    public function markSubmissionRead()
    {
        $this->checkNonce('griffinforms_mark_submission_read');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to perform this action.', 'griffinforms-form-builder'));
        }

        $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
        if ($submission_id <= 0 || !$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('Invalid submission.', 'griffinforms-form-builder'));
        }

        $value = isset($_POST['value']) ? (int) (bool) $_POST['value'] : 1;

        global $wpdb;
        $table   = $this->config->getTable('submission');
        $updated = $wpdb->update($table, ['is_read' => $value], ['id' => $submission_id], ['%d'], ['%d']);

        if ($updated === false && $wpdb->last_error) {
            $this->returnError(__('Unable to update submission.', 'griffinforms-form-builder'));
        }

        $unread_count = (int) $wpdb->get_var(
            $wpdb->prepare("SELECT COUNT(*) FROM %i WHERE is_read = %d", $table, 0)
        );

        $this->returnSuccess(['is_read' => $value, 'unread_count' => $unread_count]);
    }

    public function markSubmissionsReadBulk()
    {
        $this->checkNonce('griffinforms_mark_submissions_read_bulk');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to perform this action.', 'griffinforms-form-builder'));
        }

        $item_ids = isset($_POST['item_ids']) && is_array($_POST['item_ids'])
            ? array_filter(array_map('absint', wp_unslash($_POST['item_ids'])))
            : [];

        if (empty($item_ids)) {
            $this->returnError(__('No submissions selected.', 'griffinforms-form-builder'));
        }

        $value = isset($_POST['value']) ? (int) (bool) $_POST['value'] : 1;

        global $wpdb;
        $table       = $this->config->getTable('submission');
        $placeholders = implode(',', array_fill(0, count($item_ids), '%d'));

        $wpdb->query(
            $wpdb->prepare(
                "UPDATE %i SET is_read = %d WHERE id IN ($placeholders)",
                array_merge([$table, $value], $item_ids)
            )
        );

        $unread_count = (int) $wpdb->get_var(
            $wpdb->prepare("SELECT COUNT(*) FROM %i WHERE is_read = %d", $table, 0)
        );

        $this->returnSuccess(['unread_count' => $unread_count]);
    }
}
