<?php

namespace Kitgenix\PDF_Invoicing\Modules\Settings;

use Kitgenix\PDF_Invoicing\Modules\Invoicing\DocumentTypes;

defined( 'ABSPATH' ) || exit;

/**
 * Settings export/import.
 *
 * Export never includes `logo_id` (a site-specific media attachment ID that
 * would point at the wrong image, or none, on another site) and contains no
 * secrets – this plugin stores no API keys/tokens. Import validates against
 * a strict whitelist derived from Settings::get_defaults(): unknown
 * top-level keys are dropped, nested arrays are only accepted for their
 * known sub-keys, and every scalar is re-sanitized with the same rules the
 * settings form itself uses. Nothing from an import file is ever written to
 * the database unvalidated.
 */
final class SettingsTransfer {

    private const EXCLUDED_KEYS = [ 'logo_id' ];

    public static function export_json(): string {
        $stored = get_option( Settings::OPTION_KEY, [] );
        $stored = is_array( $stored ) ? $stored : [];

        foreach ( self::EXCLUDED_KEYS as $key ) {
            unset( $stored[ $key ] );
        }

        $payload = [
            'plugin'      => 'kitgenix-pdf-invoicing-for-woocommerce',
            'schema'      => 1,
            'exported_at' => current_time( 'mysql' ),
            'site_url'    => home_url( '/' ),
            'settings'    => $stored,
        ];

        return (string) wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
    }

    /**
     * @return array{settings:array<string,mixed>,skipped:string[]}|\WP_Error
     */
    public static function import_json( string $json ) {
        $decoded = json_decode( $json, true );

        if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $decoded ) ) {
            return new \WP_Error( 'invalid_json', __( 'That file is not valid JSON.', 'kitgenix-pdf-invoicing-for-woocommerce' ) );
        }

        $incoming = isset( $decoded['settings'] ) && is_array( $decoded['settings'] ) ? $decoded['settings'] : $decoded;

        $defaults = Settings::get_defaults();
        $clean    = [];
        $skipped  = [];

        foreach ( $incoming as $key => $value ) {
            $key = is_string( $key ) ? $key : '';

            if ( '' === $key || in_array( $key, self::EXCLUDED_KEYS, true ) || ! array_key_exists( $key, $defaults ) ) {
                if ( '' !== $key ) {
                    $skipped[] = $key;
                }
                continue;
            }

            $clean[ $key ] = self::sanitize_value_like( $key, $value, $defaults[ $key ] );
        }

        if ( empty( $clean ) ) {
            return new \WP_Error( 'no_valid_settings', __( 'No recognized settings were found in that file.', 'kitgenix-pdf-invoicing-for-woocommerce' ) );
        }

        // Merge onto the current stored option (not onto bare defaults) so
        // an import only overwrites the keys it actually contains.
        $current = get_option( Settings::OPTION_KEY, [] );
        $current = is_array( $current ) ? $current : [];
        $merged  = array_merge( $current, $clean );

        update_option( Settings::OPTION_KEY, $merged, false );

        return [
            'settings' => $clean,
            'skipped'  => $skipped,
        ];
    }

    /**
     * @param mixed $value
     * @param mixed $default_value
     * @return mixed
     */
    private static function sanitize_value_like( string $key, $value, $default_value ) {
        if ( 'document_generation_rules' === $key ) {
            return self::sanitize_document_generation_rules( is_array( $value ) ? $value : [] );
        }

        if ( 'email_attachments' === $key ) {
            return self::sanitize_email_attachments( is_array( $value ) ? $value : [] );
        }

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

        if ( is_int( $default_value ) ) {
            return absint( $value );
        }

        if ( str_ends_with( $key, '_color' ) || str_ends_with( $key, '_bg' ) ) {
            $color = sanitize_hex_color( (string) $value );
            return is_string( $color ) && '' !== $color ? $color : $default_value;
        }

        // Multi-line free text fields render through wp_kses_post()/
        // wp_strip_all_tags() at output time (see DocumentDisplay /
        // templates), so importing with wp_kses_post() here is consistent
        // with how the settings form itself accepts these fields.
        if ( in_array( $key, [ 'document_notes', 'custom_order_fields' ], true ) ) {
            return wp_kses_post( (string) $value );
        }

        return sanitize_text_field( (string) $value );
    }

    /**
     * @return array<string,array<string,mixed>>
     */
    private static function sanitize_document_generation_rules( array $incoming ): array {
        $clean = [];

        foreach ( Settings::get_default_document_generation_rules() as $type => $rule_defaults ) {
            $rule = isset( $incoming[ $type ] ) && is_array( $incoming[ $type ] ) ? $incoming[ $type ] : [];

            $clean[ $type ] = [
                'enabled'             => ! empty( $rule['enabled'] ),
                'allowed_statuses'    => isset( $rule['allowed_statuses'] ) ? sanitize_text_field( (string) $rule['allowed_statuses'] ) : (string) $rule_defaults['allowed_statuses'],
                'payment_requirement' => isset( $rule['payment_requirement'] ) && in_array( $rule['payment_requirement'], [ 'any', 'paid', 'unpaid' ], true )
                    ? (string) $rule['payment_requirement']
                    : (string) $rule_defaults['payment_requirement'],
            ];
        }

        return $clean;
    }

    /**
     * @return array<string,array<string,bool>>
     */
    private static function sanitize_email_attachments( array $incoming ): array {
        $clean         = [];
        $document_types = DocumentTypes::all();

        foreach ( Settings::get_default_email_attachments() as $email_id => $defaults_for_email ) {
            $incoming_for_email = isset( $incoming[ $email_id ] ) && is_array( $incoming[ $email_id ] ) ? $incoming[ $email_id ] : [];

            $clean_for_email = [];
            foreach ( $document_types as $doc_type ) {
                $clean_for_email[ $doc_type ] = ! empty( $incoming_for_email[ $doc_type ] );
            }

            $clean[ $email_id ] = $clean_for_email;
        }

        return $clean;
    }
}
