<?php
namespace WebshoptigerCatalogSync;

defined( 'ABSPATH' ) || exit;

/**
 * CategorySync
 *
 * Ensures categories exist on the destination site before a product is
 * synced.
 *
 * Storage: remote category IDs are stored in wp_options under a permanent
 * key (no transient with an expiry) so parallel batch processes don't
 * create the same category twice.
 */
class CategorySync {

    // -------------------------------------------------------------------------
    // Public entry point
    // -------------------------------------------------------------------------

    public static function resolve( array $local_cat_ids, array $site ): array {
        $result = [];
        foreach ( $local_cat_ids as $local_id ) {
            $remote_id = self::ensure_category( (int) $local_id, $site );
            if ( $remote_id ) {
                $result[] = [ 'id' => $remote_id ];
            }
        }
        return $result;
    }

    // -------------------------------------------------------------------------
    // Ensure the category exists on the destination site
    // -------------------------------------------------------------------------

    private static function ensure_category( int $local_id, array $site ): ?int {
        // Check the local database cache first
        $term = get_term( $local_id, 'product_cat' );
        if ( ! $term || is_wp_error( $term ) ) {
            return null;
        }

        // Check cache — but validate that the remote ID still exists
        $cached = self::get_cached_id( $site['id'], $local_id );
        if ( $cached ) {
            if ( self::remote_category_exists( $cached, $site ) ) {
                return $cached;
            }
            // Remote category was removed — clear from cache and look up again
            self::remove_cached_id( $site['id'], $local_id );
        }

        // Create the parent first
        $remote_parent_id = 0;
        if ( $term->parent ) {
            $remote_parent_id = self::ensure_category( $term->parent, $site ) ?? 0;
        }

        // Look up on destination site — slug AND name to prevent duplicates
        $remote_id = self::find_remote_category( $term->slug, $term->name, $site );

        if ( ! $remote_id ) {
            $remote_id = self::create_remote_category( $term, $remote_parent_id, $site );
        }

        if ( $remote_id ) {
            self::cache_id( $site['id'], $local_id, $remote_id );
        }

        return $remote_id;
    }

    // -------------------------------------------------------------------------
    // Look up a category on the destination site (slug + name check)
    // -------------------------------------------------------------------------

    private static function find_remote_category( string $slug, string $name, array $site ): ?int {
        $url   = trailingslashit( $site['site_url'] ) . 'wp-json/wc/v3/products/categories';
        $creds = base64_encode( $site['api_key'] . ':' . $site['api_secret'] );

        // First, look for an exact slug match
        $response = wp_remote_get( add_query_arg( [ 'slug' => $slug, 'per_page' => 10 ], $url ), [
            'timeout' => 15,
            'headers' => [ 'Authorization' => 'Basic ' . $creds ],
        ] );

        if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
            $body = json_decode( wp_remote_retrieve_body( $response ), true );
            if ( ! empty( $body ) ) {
                // Exact slug match — take the first one
                foreach ( $body as $cat ) {
                    if ( ! empty( $cat['id'] ) ) {
                        return (int) $cat['id'];
                    }
                }
            }
        }

        // Fallback: search by name (covers cases where the slug differs slightly)
        $response = wp_remote_get( add_query_arg( [
            'search'   => $name,
            'per_page' => 20,
            'hide_empty' => 'false',
        ], $url ), [
            'timeout' => 15,
            'headers' => [ 'Authorization' => 'Basic ' . $creds ],
        ] );

        if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
            $body = json_decode( wp_remote_retrieve_body( $response ), true );
            foreach ( (array) $body as $cat ) {
                // Exact name match (case-insensitive)
                if ( ! empty( $cat['id'] ) && strtolower( $cat['name'] ) === strtolower( $name ) ) {
                    return (int) $cat['id'];
                }
            }
        }

        return null;
    }

    // -------------------------------------------------------------------------
    // Create the category on the destination site
    // -------------------------------------------------------------------------

    private static function create_remote_category( \WP_Term $term, int $remote_parent_id, array $site ): ?int {
        $url   = trailingslashit( $site['site_url'] ) . 'wp-json/wc/v3/products/categories';
        $creds = base64_encode( $site['api_key'] . ':' . $site['api_secret'] );

        $payload = [
            'name'   => $term->name,
            'slug'   => $term->slug,
            'parent' => $remote_parent_id,
        ];

        $thumbnail_id = get_term_meta( $term->term_id, 'thumbnail_id', true );
        if ( $thumbnail_id ) {
            $img_url = wp_get_attachment_url( (int) $thumbnail_id );
            if ( $img_url ) {
                $payload['image'] = [ 'src' => $img_url ];
            }
        }

        if ( ! empty( $term->description ) ) {
            $payload['description'] = self::get_description( $term );
        }

        $response = wp_remote_post( $url, [
            'timeout' => 20,
            'headers' => [
                'Authorization' => 'Basic ' . $creds,
                'Content-Type'  => 'application/json',
            ],
            'body' => wp_json_encode( $payload ),
        ] );

        if ( is_wp_error( $response ) ) {
            return null;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( $code === 201 && ! empty( $body['id'] ) ) {
            return (int) $body['id'];
        }

        // Category already exists (race condition or slug conflict) — look it up
        if ( $code === 400 ) {
            return self::find_remote_category( $term->slug, $term->name, $site );
        }

        return null;
    }

    // -------------------------------------------------------------------------
    // Database cache — persistent storage in wp_options
    // -------------------------------------------------------------------------

    private static function option_key( int $site_id ): string {
        return 'webshoptigercatalogsync_cat_map_' . $site_id;
    }

    private static function get_cached_id( int $site_id, int $local_id ): ?int {
        $map = get_option( self::option_key( $site_id ), [] );
        return isset( $map[ $local_id ] ) ? (int) $map[ $local_id ] : null;
    }

    private static function cache_id( int $site_id, int $local_id, int $remote_id ): void {
        $key = self::option_key( $site_id );
        $map = get_option( $key, [] );
        $map[ $local_id ] = $remote_id;
        update_option( $key, $map, false );
    }

    private static function remove_cached_id( int $site_id, int $local_id ): void {
        $key = self::option_key( $site_id );
        $map = get_option( $key, [] );
        unset( $map[ $local_id ] );
        update_option( $key, $map, false );
    }

    /**
     * Check whether a remote category still exists on the destination site.
     * Uses an in-memory cache to avoid duplicate API calls.
     */
    private static $existence_cache = [];

    private static function remote_category_exists( int $remote_id, array $site ): bool {
        $key = $site['id'] . '_' . $remote_id;
        if ( isset( self::$existence_cache[ $key ] ) ) {
            return self::$existence_cache[ $key ];
        }

        $url   = trailingslashit( $site['site_url'] ) . 'wp-json/wc/v3/products/categories/' . $remote_id;
        $creds = base64_encode( $site['api_key'] . ':' . $site['api_secret'] );

        $response = wp_remote_get( $url, [
            'timeout' => 8,
            'headers' => [ 'Authorization' => 'Basic ' . $creds ],
        ] );

        $exists = ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200;
        self::$existence_cache[ $key ] = $exists;

        return $exists;
    }

    /**
     * Validate the entire cache for a site — remove IDs that no longer
     * exist. Call this at the start of a sync run, not per product.
     * Fetches all remote categories in a single API call.
     */
    public static function validate_cache( array $site ): void {
        $map = get_option( self::option_key( $site['id'] ), [] );
        if ( empty( $map ) ) return;

        // Fetch all categories from the destination site (max 100 per page)
        $url   = trailingslashit( $site['site_url'] ) . 'wp-json/wc/v3/products/categories';
        $creds = base64_encode( $site['api_key'] . ':' . $site['api_secret'] );

        $remote_ids  = [];
        $page        = 1;
        do {
            $response = wp_remote_get( add_query_arg( [
                'per_page'   => 100,
                'page'       => $page,
                'hide_empty' => 'false',
                '_fields'    => 'id',
            ], $url ), [
                'timeout' => 15,
                'headers' => [ 'Authorization' => 'Basic ' . $creds ],
            ] );

            if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) break;

            $batch = json_decode( wp_remote_retrieve_body( $response ), true ) ?: [];
            foreach ( $batch as $cat ) {
                $remote_ids[ (int) $cat['id'] ] = true;
            }
            $page++;
        } while ( count( $batch ) === 100 );

        if ( empty( $remote_ids ) ) return;

        // Remove cached IDs that no longer exist
        $changed = false;
        foreach ( $map as $local_id => $remote_id ) {
            if ( ! isset( $remote_ids[ (int) $remote_id ] ) ) {
                unset( $map[ $local_id ] );
                $changed = true;
            }
        }

        if ( $changed ) {
            update_option( self::option_key( $site['id'] ), $map, false );
        }

        // Fill in-memory existence cache
        self::$existence_cache = [];
        foreach ( $remote_ids as $id => $_ ) {
            self::$existence_cache[ $site['id'] . '_' . $id ] = true;
        }
    }

    public static function clear_cache( int $site_id ): void {
        delete_option( self::option_key( $site_id ) );
        self::$existence_cache = [];
        global $wpdb;
        // phpcs:ignore // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query( $wpdb->prepare(
            "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
            '_transient_sc_cat_' . $site_id . '_%',
            '_transient_timeout_sc_cat_' . $site_id . '_%'
        ) );
    }

    // -------------------------------------------------------------------------
    // Rewrite description via OpenAI
    // -------------------------------------------------------------------------

    private static function get_description( \WP_Term $term ): string {
        $original   = $term->description;
        $rewrite_on = (int) Settings::get( 'rewrite_descriptions' );

        if ( ! $rewrite_on || empty( trim( $original ) ) ) {
            return $original;
        }

        $hash       = md5( $original );
        $cache_meta = '_sc_cat_desc_hash_' . $term->term_id;
        $saved_hash = get_term_meta( $term->term_id, $cache_meta, true );
        $saved_text = get_term_meta( $term->term_id, '_sc_cat_desc_rewritten_' . $term->term_id, true );

        if ( $saved_hash === $hash && ! empty( $saved_text ) ) {
            return $saved_text;
        }

        $rewritten = OpenAI::rewrite_description_text( $original );
        if ( is_wp_error( $rewritten ) || empty( $rewritten ) ) {
            return $original;
        }

        update_term_meta( $term->term_id, $cache_meta, $hash );
        update_term_meta( $term->term_id, '_sc_cat_desc_rewritten_' . $term->term_id, $rewritten );

        return $rewritten;
    }
}
