<?php

namespace Formatrica\Forms;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Formatrica\Capability\Premium_Save_Guard;
use wpdb;

final class Form_Repository
{
    private wpdb $wpdb;
    private string $table;

    public function __construct(?wpdb $db = null)
    {
        global $wpdb;
        $this->wpdb = $db ?? $wpdb;
        $this->table = $this->wpdb->prefix . 'formatrica_forms';
    }

    /**
     * Retrieve a single form with config payload.
     */
    public function get(int $form_id): ?array
    {
        if ($form_id <= 0) {
            return null;
        }

        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- %i identifier placeholder is valid since WP 6.2
        $row = $this->wpdb->get_row(
            $this->wpdb->prepare(
                'SELECT id, title, fields, settings, active, created_at, updated_at FROM %i WHERE id = %d LIMIT 1',
                $this->table,
                $form_id
            ),
            ARRAY_A
        );
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared

        if (!$row) {
            return null;
        }

        return $this->map_row_to_form($row);
    }

    /**
     * List all active forms (optionally with pagination in future).
     *
     * @return array<int, array<string, mixed>>
     */
    public function all(?string $status = 'active'): array
    {
        $status_map = ['active' => 1, 'inactive' => 0];
        $status = $status ?? 'active';

        if (isset($status_map[$status])) {
            $sql = $this->wpdb->prepare(
                'SELECT id, title, fields, settings, active, created_at, updated_at FROM %i WHERE active = %d ORDER BY created_at DESC',
                $this->table,
                $status_map[$status]
            );
        } else {
            $sql = $this->wpdb->prepare(
                'SELECT id, title, fields, settings, active, created_at, updated_at FROM %i ORDER BY created_at DESC',
                $this->table
            );
        }

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is built via $wpdb->prepare() above
        $rows = $this->wpdb->get_results($sql, ARRAY_A);

        if (empty($rows)) {
            return [];
        }

        return array_values(array_map([$this, 'map_row_to_form'], $rows));
    }

    /**
     * Return totals per status for list views.
     *
     * @return array{all:int,active:int,inactive:int}
     */
    public function counts(): array
    {
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- %i identifier placeholder is valid since WP 6.2
        $all = (int) $this->wpdb->get_var($this->wpdb->prepare('SELECT COUNT(*) FROM %i', $this->table));
        $active = (int) $this->wpdb->get_var($this->wpdb->prepare('SELECT COUNT(*) FROM %i WHERE active = 1', $this->table));
        $inactive = (int) $this->wpdb->get_var($this->wpdb->prepare('SELECT COUNT(*) FROM %i WHERE active = 0', $this->table));
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared

        return [
            'all' => $all,
            'active' => $active,
            'inactive' => $inactive,
        ];
    }

    /**
     * Create or update a form.
     *
     * @param array<string, mixed> $payload
     */
    public function save(?int $form_id, array $payload): array
    {
        $existing_config = $form_id ? $this->load_config($form_id) : [];
        $sanitised = Form_Sanitizer::sanitise_payload($payload, ['config' => $existing_config]);

        $title = $sanitised['title'];
        $config = $sanitised['config'];

        // Fail-closed premium enforcement: revert unauthorized Pro config to its
        // previously-stored value and collect rejections for the save response.
        // Runs here so every save path is protected, not just REST. The form id
        // rides along in $existing for rules that need cross-form context
        // (Webhook_Unlimited_Rule excludes the form being saved from its count).
        $existing_config['id'] = (int) ($form_id ?? 0);
        $rejected = Premium_Save_Guard::default()->enforce($config, $existing_config);
        unset($existing_config['id']);

        $fields = isset($config['fields']) && is_array($config['fields']) ? $config['fields'] : [];
        $settings = isset($config['options']) && is_array($config['options']) ? $config['options'] : [];

        $fields_json = wp_json_encode($fields, JSON_UNESCAPED_UNICODE);
        $settings_json = wp_json_encode($settings, JSON_UNESCAPED_UNICODE);

        if ($fields_json === false || !is_string($fields_json) || $fields_json === '') {
            $error = function_exists('json_last_error_msg') ? json_last_error_msg() : 'JSON encoding failed';
            /* translators: %s: JSON error message */
            throw new \RuntimeException(esc_html(sprintf(__('Failed to encode form fields: %s', 'formatrica'), $error)));
        }

        if ($settings_json === false || !is_string($settings_json) || $settings_json === '') {
            $error = function_exists('json_last_error_msg') ? json_last_error_msg() : 'JSON encoding failed';
            /* translators: %s: JSON error message */
            throw new \RuntimeException(esc_html(sprintf(__('Failed to encode form settings: %s', 'formatrica'), $error)));
        }

        if ($form_id && $form_id > 0) {
            // Update existing form
            $result = $this->wpdb->update(
                $this->table,
                [
                    'title' => $title,
                    'fields' => $fields_json,
                    'settings' => $settings_json,
                ],
                ['id' => $form_id],
                ['%s', '%s', '%s'],
                ['%d']
            );

            if ($result === false) {
                throw new \RuntimeException(esc_html(sprintf(
                    /* translators: %s: database error message */
                    __('Failed to update form: %s', 'formatrica'),
                    $this->wpdb->last_error
                )));
            }

            $saved_form_id = $form_id;
        } else {
            // Insert new form
            $result = $this->wpdb->insert(
                $this->table,
                [
                    'title' => $title,
                    'fields' => $fields_json,
                    'settings' => $settings_json,
                    'active' => 1,
                ],
                ['%s', '%s', '%s', '%d']
            );

            if ($result === false) {
                throw new \RuntimeException(esc_html(sprintf(
                    /* translators: %s: database error message */
                    __('Failed to create form: %s', 'formatrica'),
                    $this->wpdb->last_error
                )));
            }

            $saved_form_id = (int) $this->wpdb->insert_id;
        }

        $form = $this->get($saved_form_id);

        if (null === $form) {
            throw new \RuntimeException(esc_html__('Unable to load form after saving.', 'formatrica'));
        }

        // Surface any reverted premium config so the builder can notify the user.
        $form['rejected'] = $rejected;

        return $form;
    }

    public function duplicate(int $form_id): int
    {
        $original = $this->get($form_id);

        if (null === $original) {
            throw new \RuntimeException(esc_html__('Original form could not be found.', 'formatrica'));
        }

        $raw_config = $this->raw_config($form_id);

        $title = $original['title'];
        if ($title === '') {
            $title = __('Untitled form', 'formatrica');
        }

        /* translators: %s: original form title */
        $copy_title = apply_filters('formatrica_duplicate_title', sprintf(__('%s (Copy)', 'formatrica'), $title), $original);

        $payload = [
            'title' => $copy_title,
            'config' => $raw_config,
        ];

        $sanitised = Form_Sanitizer::sanitise_payload($payload, ['config' => $raw_config]);

        $title = $sanitised['title'];
        $config = $sanitised['config'];

        $fields = isset($config['fields']) && is_array($config['fields']) ? $config['fields'] : [];
        $settings = isset($config['options']) && is_array($config['options']) ? $config['options'] : [];

        $fields_json = wp_json_encode($fields, JSON_UNESCAPED_UNICODE);
        $settings_json = wp_json_encode($settings, JSON_UNESCAPED_UNICODE);

        if ($fields_json === false || !is_string($fields_json) || $fields_json === '') {
            $error = function_exists('json_last_error_msg') ? json_last_error_msg() : 'JSON encoding failed';
            /* translators: %s: JSON error message */
            throw new \RuntimeException(esc_html(sprintf(__('Failed to encode form fields: %s', 'formatrica'), $error)));
        }

        if ($settings_json === false || !is_string($settings_json) || $settings_json === '') {
            $error = function_exists('json_last_error_msg') ? json_last_error_msg() : 'JSON encoding failed';
            /* translators: %s: JSON error message */
            throw new \RuntimeException(esc_html(sprintf(__('Failed to encode form settings: %s', 'formatrica'), $error)));
        }

        $result = $this->wpdb->insert(
            $this->table,
            [
                'title' => $title,
                'fields' => $fields_json,
                'settings' => $settings_json,
                'active' => 1,
            ],
            ['%s', '%s', '%s', '%d']
        );

        if ($result === false) {
            throw new \RuntimeException(esc_html(sprintf(
                /* translators: %s: database error message */
                __('Failed to duplicate form: %s', 'formatrica'),
                $this->wpdb->last_error
            )));
        }

        return (int) $this->wpdb->insert_id;
    }

    public function delete(int $form_id): bool
    {
        if ($form_id <= 0) {
            return false;
        }

        // Soft delete by setting active = 0
        // For hard delete, use: $this->wpdb->delete($this->table, ['id' => $form_id], ['%d']);
        $result = $this->wpdb->update(
            $this->table,
            ['active' => 0],
            ['id' => $form_id],
            ['%d'],
            ['%d']
        );

        return $result !== false;
    }

    /**
     * Reactivate a previously deactivated form (sets active = 1).
     */
    public function activate(int $form_id): bool
    {
        if ($form_id <= 0) {
            return false;
        }

        $result = $this->wpdb->update(
            $this->table,
            ['active' => 1],
            ['id' => $form_id],
            ['%d'],
            ['%d']
        );

        return $result !== false;
    }

    /**
     * Hard delete a form (permanently removes from database)
     */
    public function hard_delete(int $form_id): bool
    {
        if ($form_id <= 0) {
            return false;
        }

        $result = $this->wpdb->delete(
            $this->table,
            ['id' => $form_id],
            ['%d']
        );

        return $result !== false;
    }

    /**
     * Legacy meta_key method (kept for backward compatibility)
     */
    public static function meta_key(): string
    {
        return '_formatrica_config';
    }

    /**
     * Map database row to form array
     *
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function map_row_to_form(array $row): array
    {
        $config = $this->load_config_from_row($row);
        $merged = Form_Sanitizer::merge_with_defaults($config);
        $raw_options = isset($config['options']) && is_array($config['options'])
            ? $config['options']
            : [];

        // Redact sensitive data
        if (isset($merged['options']['email_delivery']['smtp'])) {
            if (!empty($raw_options['email_delivery']['smtp']['password'] ?? '')) {
                $merged['options']['email_delivery']['smtp']['password_set'] = true;
            }
            $merged['options']['email_delivery']['smtp']['password'] = '';
        }

        // Delivery provider API keys are write-only (Phase 6): the builder gets
        // only the *_set flag, never the stored (encrypted or legacy) value.
        if (isset($merged['options']['email_delivery']) && is_array($merged['options']['email_delivery'])) {
            foreach ([
                ['sendgrid', 'api_key'],
                ['mailgun', 'api_key'],
                ['postmark', 'api_token'],
                ['brevo', 'api_key'],
                ['amazon_ses', 'secret_key'],
                ['smtp2go', 'api_key'],
            ] as [$prov, $key]) {
                if (!isset($merged['options']['email_delivery'][$prov])) {
                    continue;
                }
                if (!empty($raw_options['email_delivery'][$prov][$key] ?? '')) {
                    $merged['options']['email_delivery'][$prov][$key . '_set'] = true;
                }
                $merged['options']['email_delivery'][$prov][$key] = '';
            }
        }

        if (isset($merged['options']['webhooks']['secret'])) {
            $merged['options']['webhooks']['secret'] = '';
            if (!empty($raw_options['webhooks']['secret'] ?? '')) {
                $merged['options']['webhooks']['secret_set'] = true;
            }
        }

        if (isset($merged['options']['captcha']['secret_key'])) {
            if (!empty($raw_options['captcha']['secret_key'] ?? '')) {
                $merged['options']['captcha']['secret_key_set'] = true;
            }
            $merged['options']['captcha']['secret_key'] = '';
        }

        // Headless API key (Pro api.headless): write-only like every other
        // secret — the builder gets only the *_set flag, never the stored blob.
        if (isset($merged['options']['headless_api_key'])) {
            if (!empty($raw_options['headless_api_key'] ?? '')) {
                $merged['options']['headless_api_key_set'] = true;
            }
            $merged['options']['headless_api_key'] = '';
        }

        if (isset($merged['options']['integrations']) && is_array($merged['options']['integrations'])) {
            $raw_integrations = isset($config['options']['integrations']) && is_array($config['options']['integrations'])
                ? $config['options']['integrations']
                : [];
            $merged['options']['integrations'] = $this->redact_integration_secrets(
                $merged['options']['integrations'],
                $raw_integrations
            );
        }

        return [
            'id' => (int) $row['id'],
            'title' => $row['title'],
            'config' => $merged,
            'date' => $row['created_at'],
            'active' => (int) ($row['active'] ?? 1),
        ];
    }

    /**
     * @param array<string, mixed> $integrations
     * @param array<string, mixed> $raw_integrations
     * @return array<string, mixed>
     */
    private function redact_integration_secrets(array $integrations, array $raw_integrations): array
    {
        foreach ($integrations as $integration_id => &$settings) {
            if (!is_array($settings)) {
                continue;
            }

            $raw_settings = isset($raw_integrations[$integration_id]) && is_array($raw_integrations[$integration_id])
                ? $raw_integrations[$integration_id]
                : [];

            switch ($integration_id) {
                case 'mailchimp':
                case 'brevo':
                    if (isset($raw_settings['api_key']) && $raw_settings['api_key'] !== '') {
                        $settings['api_key_set'] = true;
                    }
                    $settings['api_key'] = '';
                    break;
                case 'hubspot':
                    if (isset($raw_settings['access_token']) && $raw_settings['access_token'] !== '') {
                        $settings['access_token_set'] = true;
                    }
                    $settings['access_token'] = '';
                    break;
                case 'twilio':
                    if (isset($raw_settings['api_key_secret']) && $raw_settings['api_key_secret'] !== '') {
                        $settings['api_key_secret_set'] = true;
                    }
                    $settings['api_key_secret'] = '';
                    break;
                case 'make':
                    if (isset($raw_settings['api_key']) && $raw_settings['api_key'] !== '') {
                        $settings['api_key_set'] = true;
                    }
                    $settings['api_key'] = '';
                    break;
                default:
                    // No redaction needed
                    break;
            }
        }
        unset($settings);

        return $integrations;
    }

    /**
     * Load form configuration from database row
     *
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function load_config_from_row(array $row): array
    {
        $fields = [];
        $settings = [];

        if (!empty($row['fields'])) {
            $decoded_fields = json_decode($row['fields'], true);
            if (is_array($decoded_fields)) {
                $fields = $decoded_fields;
            }
        }

        if (!empty($row['settings'])) {
            $decoded_settings = json_decode($row['settings'], true);
            if (is_array($decoded_settings)) {
                $settings = $decoded_settings;
            }
        }

        return [
            'fields' => $fields,
            'options' => $settings,
        ];
    }

    /**
     * Load config for a specific form ID
     *
     * @return array<string, mixed>
     */
    private function load_config(int $form_id): array
    {
        if ($form_id <= 0) {
            return [];
        }

        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- %i identifier placeholder is valid since WP 6.2
        $row = $this->wpdb->get_row(
            $this->wpdb->prepare(
                'SELECT fields, settings FROM %i WHERE id = %d LIMIT 1',
                $this->table,
                $form_id
            ),
            ARRAY_A
        );
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared

        if (!$row) {
            return [];
        }

        return $this->load_config_from_row($row);
    }

    /**
     * Get raw config (without merging defaults)
     *
     * @return array<string, mixed>
     */
    public function raw_config(int $form_id): array
    {
        return $this->load_config($form_id);
    }
}
