<?php
if (!defined('ABSPATH')) exit;

class Calliope_SP_Instagram {

    private $api_base = 'https://graph.instagram.com/v24.0';

    /**
     * Meta fetches media from the public URL immediately after we create the file.
     * Some hosts/CDNs need a brief moment before the new upload is reachable externally.
     *
     * @param string $url
     * @param int    $max_attempts
     * @return true|WP_Error
     */
    private function wait_for_public_image_url($url, $max_attempts = 6) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return new WP_Error('upload_failed', 'Missing public image URL.');
        }

        $request_args = [
            'timeout'     => 20,
            'redirection' => 3,
            'user-agent'  => 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
        ];

        for ($attempt = 1; $attempt <= (int) $max_attempts; $attempt++) {
            $head = wp_remote_head($url, $request_args);
            if (!is_wp_error($head)) {
                $code = (int) wp_remote_retrieve_response_code($head);
                $type = strtolower((string) wp_remote_retrieve_header($head, 'content-type'));
                $len  = (string) wp_remote_retrieve_header($head, 'content-length');

                if ($code === 200 && strpos($type, 'image/') === 0 && ($len === '' || (int) $len > 0)) {
                    return true;
                }
            }

            $get = wp_remote_get($url, array_merge($request_args, [
                'headers' => [
                    'Range' => 'bytes=0-1023',
                ],
            ]));

            if (!is_wp_error($get)) {
                $code = (int) wp_remote_retrieve_response_code($get);
                $type = strtolower((string) wp_remote_retrieve_header($get, 'content-type'));
                $body = (string) wp_remote_retrieve_body($get);

                if (($code === 200 || $code === 206) && strpos($type, 'image/') === 0 && $body !== '') {
                    return true;
                }
            }

            sleep(min(4, $attempt));
        }

        if (class_exists('Calliope_SP_Logger')) {
            Calliope_SP_Logger::log('instagram', 'error', 'Public image URL was not externally reachable in time: ' . $url);
        }

        return new WP_Error(
            'upload_not_public',
            'The generated image is not publicly reachable yet. Please try publishing again in a few seconds.'
        );
    }

    /**
     * Convert a base64 image into an Instagram-feed-safe public image asset.
     *
     * Instagram feed images must stay within a 4:5 to 1.91:1 aspect ratio range.
     * If the selected image is outside that range, create a centered cropped copy
     * only for Instagram instead of failing the whole publish attempt.
     *
     * @param string $b64
     * @param bool   $force_square Use a safest 1:1 crop for retry fallback.
     * @return array<string,mixed>|WP_Error
     */
    private function image_asset_from_b64($b64, $force_square = false) {
        $asset = class_exists('Calliope_SP_Media_Helper')
            ? Calliope_SP_Media_Helper::create_public_image_from_b64($b64, Calliope_SP_Media_Helper::get_meta_static_image_args('instagram'))
            : new WP_Error('instagram_invalid_media_url', 'Instagram could not prepare a valid public image URL.');

        if (is_wp_error($asset)) {
            return new WP_Error(
                'instagram_invalid_media_url',
                'Instagram could not use this image because the public media URL is not accessible or does not return a valid image. Please try another image or use Original format.',
                [
                    'stage'                  => 'prepare_public_media_url',
                    'original_error_code'    => $asset->get_error_code(),
                    'original_error_message' => $asset->get_error_message(),
                    'original_error_data'    => $asset->get_error_data(),
                ]
            );
        }

        $width = (int) ($asset['width'] ?? 0);
        $height = (int) ($asset['height'] ?? 0);
        if ($width <= 0 || $height <= 0) {
            return $asset;
        }

        $ratio = $width / $height;
        $target_ratio = 0.0;

        if ($force_square) {
            $target_ratio = 1.0;
        } elseif ($ratio < 0.8) {
            // Too tall for Instagram feed. Crop to 4:5.
            $target_ratio = 0.8;
        } elseif ($ratio > 1.91) {
            // Too wide for Instagram feed. Crop to 1.91:1.
            $target_ratio = 1.91;
        }

        if ($target_ratio <= 0) {
            return $asset;
        }

        $cropped = $this->crop_asset_for_instagram_feed($asset, $target_ratio, $force_square);
        if (is_wp_error($cropped)) {
            if (class_exists('Calliope_SP_Logger')) {
                Calliope_SP_Logger::log('instagram', 'error', wp_json_encode([
                    'message'      => 'Could not auto-crop image for Instagram feed.',
                    'width'        => $width,
                    'height'       => $height,
                    'ratio'        => round($ratio, 4),
                    'target_ratio' => $target_ratio,
                    'force_square' => (bool) $force_square,
                    'error'        => $cropped->get_error_message(),
                ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
            }

            return $cropped;
        }

        return $cropped;
    }

    /**
     * Create a centered cropped JPEG copy that matches Instagram feed ratio limits.
     *
     * @param array<string,mixed> $asset
     * @param float               $target_ratio
     * @param bool                $force_square
     * @return array<string,mixed>|WP_Error
     */
    private function crop_asset_for_instagram_feed(array $asset, $target_ratio, $force_square = false) {
        if (!function_exists('wp_get_image_editor')) {
            return new WP_Error('instagram_image_crop_failed', 'WordPress image editing is not available on this site.');
        }

        $path = (string) ($asset['local_path'] ?? $asset['path'] ?? '');
        $width = (int) ($asset['width'] ?? 0);
        $height = (int) ($asset['height'] ?? 0);
        $target_ratio = (float) $target_ratio;

        if ($path === '' || !file_exists($path) || $width <= 0 || $height <= 0 || $target_ratio <= 0) {
            return new WP_Error('instagram_image_crop_failed', 'Instagram could not prepare a compatible cropped image.');
        }

        $current_ratio = $width / $height;
        if ($current_ratio > $target_ratio) {
            $src_w = max(1, (int) floor($height * $target_ratio));
            $src_h = $height;
            $src_x = max(0, (int) floor(($width - $src_w) / 2));
            $src_y = 0;
        } else {
            $src_w = $width;
            $src_h = max(1, (int) floor($width / $target_ratio));
            $src_x = 0;
            $src_y = max(0, (int) floor(($height - $src_h) / 2));
        }

        $editor = wp_get_image_editor($path);
        if (is_wp_error($editor)) {
            return $editor;
        }

        if (method_exists($editor, 'set_quality')) {
            $editor->set_quality(90);
        }

        $cropped = $editor->crop($src_x, $src_y, $src_w, $src_h, $src_w, $src_h, false);
        if (is_wp_error($cropped)) {
            return $cropped;
        }

        $suffix = $force_square ? '-ig-feed-square' : '-ig-feed-safe';
        $target_path = preg_replace('/\.[^.]+$/', '', $path) . $suffix . '.jpg';
        $saved = $editor->save($target_path, 'image/jpeg');
        if (is_wp_error($saved) || empty($saved['path'])) {
            return is_wp_error($saved)
                ? $saved
                : new WP_Error('instagram_image_crop_failed', 'The cropped Instagram image could not be saved.');
        }

        $saved_path = (string) $saved['path'];
        $meta = class_exists('Calliope_SP_Media_Helper')
            ? Calliope_SP_Media_Helper::inspect_image_file($saved_path)
            : new WP_Error('instagram_image_crop_failed', 'Instagram could not inspect the cropped image.');

        if (is_wp_error($meta)) {
            if (class_exists('Calliope_SP_Media_Helper')) {
                Calliope_SP_Media_Helper::delete_file($saved_path);
            }
            return $meta;
        }

        if (!class_exists('Calliope_SP_Media_Helper')) {
            return new WP_Error('invalid_public_media_url', 'The cropped Instagram image URL could not be validated.');
        }

        $static_url = Calliope_SP_Media_Helper::build_public_url_from_upload_path($saved_path);
        if (is_wp_error($static_url)) {
            return $static_url;
        }
        $static_url = (string) $static_url;

        $passthrough_url = '';
        $local_diagnostics = Calliope_SP_Media_Helper::get_local_file_diagnostics($saved_path, $meta);

        // Do not block cropped Instagram publishing on a server self-fetch check.
        // Some shared hosts/CDNs fail loopback TLS requests even when the public
        // image URL is reachable by Meta. The local JPEG is already validated here.
        $url = $static_url;
        $resolved_strategy = 'meta_static_safe';
        $public_meta = [
            'http_head_status' => 0,
            'http_content_type' => (string) ($meta['mime'] ?? 'image/jpeg'),
            'http_get_status' => 0,
            'http_content_length' => (string) ($local_diagnostics['filesize'] ?? ''),
            'http_accept_ranges' => '',
            'http_cache_control' => '',
            'http_x_content_type_options' => '',
            'http_server' => '',
            'http_via' => '',
            'http_cf_cache_status' => '',
            'http_x_litespeed_cache' => '',
            'http_x_hcdn_cache_status' => '',
            'http_x_hcdn_request_id' => '',
            'body_starts_with_jpeg' => !empty($local_diagnostics['starts_with_jpeg_magic']),
            'body_detected_mime' => (string) ($meta['mime'] ?? 'image/jpeg'),
            'body_magic' => (string) ($local_diagnostics['first_12_bytes_hex'] ?? ''),
            'body_matches_expected' => true,
            'body_size_matches_expected' => true,
            'body_magic_matches_expected' => true,
            'http_content_length_matches_expected' => true,
        ];

        if (class_exists('Calliope_SP_Logger')) {
            Calliope_SP_Logger::log('instagram', 'warning', wp_json_encode([
                'message' => 'Cropped Instagram image self-check skipped; local JPEG is valid and the direct public URL will be sent to Meta.',
                'public_url' => $url,
                'url_strategy' => $resolved_strategy,
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        }

        $result = array_merge($meta, Calliope_SP_Media_Helper::get_local_file_diagnostics($saved_path, $meta), [
            'url'               => esc_url_raw($url),
            'passthrough_url'   => esc_url_raw($passthrough_url),
            'meta_static_url'   => esc_url_raw($static_url),
            'static_url'        => esc_url_raw($static_url),
            'url_strategy'      => $resolved_strategy,
            'local_path'        => $saved_path,
            'http_head_status'  => (int) ($public_meta['http_head_status'] ?? 0),
            'http_content_type' => (string) ($public_meta['http_content_type'] ?? ''),
            'http_get_status'   => (int) ($public_meta['http_get_status'] ?? 0),
            'http_content_length' => (string) ($public_meta['http_content_length'] ?? ''),
            'http_accept_ranges' => (string) ($public_meta['http_accept_ranges'] ?? ''),
            'http_cache_control' => (string) ($public_meta['http_cache_control'] ?? ''),
            'http_x_content_type_options' => (string) ($public_meta['http_x_content_type_options'] ?? ''),
            'http_server' => (string) ($public_meta['http_server'] ?? ''),
            'http_via' => (string) ($public_meta['http_via'] ?? ''),
            'http_cf_cache_status' => (string) ($public_meta['http_cf_cache_status'] ?? ''),
            'http_x_litespeed_cache' => (string) ($public_meta['http_x_litespeed_cache'] ?? ''),
            'http_x_hcdn_cache_status' => (string) ($public_meta['http_x_hcdn_cache_status'] ?? ''),
            'http_x_hcdn_request_id' => (string) ($public_meta['http_x_hcdn_request_id'] ?? ''),
            'body_starts_with_jpeg' => (bool) ($public_meta['body_starts_with_jpeg'] ?? false),
            'body_detected_mime' => (string) ($public_meta['body_detected_mime'] ?? ''),
            'body_magic' => (string) ($public_meta['body_magic'] ?? ''),
            'body_matches_local' => (bool) ($public_meta['body_matches_expected'] ?? false),
            'body_size_matches_local' => (bool) ($public_meta['body_size_matches_expected'] ?? false),
            'body_magic_matches_local' => (bool) ($public_meta['body_magic_matches_expected'] ?? false),
            'http_content_length_matches_local' => (bool) ($public_meta['http_content_length_matches_expected'] ?? false),
        ]);

        if (class_exists('Calliope_SP_Logger')) {
            Calliope_SP_Logger::log('instagram', 'info', wp_json_encode([
                'message'         => 'Image was auto-cropped for Instagram feed compatibility.',
                'original_width'  => $width,
                'original_height' => $height,
                'original_ratio'  => round($current_ratio, 4),
                'target_ratio'    => $target_ratio,
                'new_width'       => (int) ($result['width'] ?? 0),
                'new_height'      => (int) ($result['height'] ?? 0),
                'force_square'    => (bool) $force_square,
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        }

        return $result;
    }

    /**
     * @param string $message Graph error message.
     * @return bool
     */
    private function is_aspect_ratio_not_supported_error($message) {
        $message = strtolower((string) $message);
        return strpos($message, 'aspect ratio is not supported') !== false
            || strpos($message, 'unsupported aspect ratio') !== false
            || strpos($message, 'invalid aspect ratio') !== false;
    }

    private function request($method, $endpoint, $token, $body = null) {
        $url = rtrim($this->api_base, '/') . '/' . ltrim($endpoint, '/');

        $args = [
            'method'  => strtoupper($method),
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
            ],
            'timeout' => 60,
        ];

        if ($body !== null) {
            if (strtoupper($method) === 'POST') {
                // Meta Graph endpoints are stricter with media creation; send fields as standard form parameters.
                $args['body'] = is_array($body) ? $body : (string) $body;
            } else {
                $args['headers']['Content-Type'] = 'application/json';
                $args['body'] = is_array($body) ? wp_json_encode($body) : (string) $body;
            }
        }

        $res = wp_remote_request($url, $args);

        if (is_wp_error($res)) {
            if (class_exists('Calliope_SP_Logger')) {
                Calliope_SP_Logger::log('instagram', 'error', 'HTTP WP_Error: ' . $res->get_error_message());
            }
            return $res;
        }

        $code = (int) wp_remote_retrieve_response_code($res);
        $raw  = (string) wp_remote_retrieve_body($res);

        $json = null;
        $decoded = json_decode($raw, true);
        if (is_array($decoded)) $json = $decoded;

        if (class_exists('Calliope_SP_Logger')) {
            $lvl = ($code >= 200 && $code < 300) ? 'info' : 'error';
            $msg = 'HTTP ' . $code . ' | ' . mb_substr($raw, 0, 1200);
            Calliope_SP_Logger::log('instagram', $lvl, $msg);
        }

        return [
            'code'      => $code,
            'body_raw'  => $raw,
            'body_json' => $json,
        ];
    }

    /**
     * Build structured Graph/API diagnostics that are preserved in publisher logs.
     *
     * @param array<string,mixed> $resp
     * @param array<string,mixed> $image_asset
     * @param array<string,mixed> $extra
     * @return array<string,mixed>
     */
    private function build_graph_error_data(array $resp, array $image_asset = [], array $extra = []) {
        $json = isset($resp['body_json']) && is_array($resp['body_json']) ? $resp['body_json'] : [];
        $err = isset($json['error']) && is_array($json['error']) ? $json['error'] : [];

        return array_merge([
            'http_code'           => (int) ($resp['code'] ?? 0),
            'graph_error_message' => (string) ($err['message'] ?? ''),
            'graph_error_type'    => (string) ($err['type'] ?? ''),
            'graph_error_code'    => (int) ($err['code'] ?? 0),
            'graph_error_subcode' => (int) ($err['error_subcode'] ?? 0),
            'fbtrace_id'          => (string) ($err['fbtrace_id'] ?? ''),
            'raw_response'        => mb_substr((string) ($resp['body_raw'] ?? ''), 0, 1600),
            'image_url'           => (string) ($image_asset['url'] ?? ''),
            'passthrough_url'     => (string) ($image_asset['passthrough_url'] ?? ''),
            'meta_static_url'     => (string) ($image_asset['meta_static_url'] ?? ''),
            'static_url'          => (string) ($image_asset['static_url'] ?? ''),
            'url_strategy'        => (string) ($image_asset['url_strategy'] ?? ''),
            'mime'                => (string) ($image_asset['mime'] ?? ''),
            'width'               => (int) ($image_asset['width'] ?? 0),
            'height'              => (int) ($image_asset['height'] ?? 0),
            'filesize'            => (int) ($image_asset['filesize'] ?? 0),
            'local_path'          => (string) ($image_asset['local_path'] ?? $image_asset['path'] ?? ''),
            'http_head_status'    => (int) ($image_asset['http_head_status'] ?? 0),
            'http_get_status'     => (int) ($image_asset['http_get_status'] ?? 0),
            'http_content_type'   => (string) ($image_asset['http_content_type'] ?? ''),
            'http_content_length' => (string) ($image_asset['http_content_length'] ?? ''),
            'http_accept_ranges'  => (string) ($image_asset['http_accept_ranges'] ?? ''),
            'http_cache_control'  => (string) ($image_asset['http_cache_control'] ?? ''),
            'http_x_content_type_options' => (string) ($image_asset['http_x_content_type_options'] ?? ''),
            'http_server'         => (string) ($image_asset['http_server'] ?? ''),
            'http_via'            => (string) ($image_asset['http_via'] ?? ''),
            'http_cf_cache_status'=> (string) ($image_asset['http_cf_cache_status'] ?? ''),
            'http_x_litespeed_cache' => (string) ($image_asset['http_x_litespeed_cache'] ?? ''),
            'http_x_hcdn_cache_status' => (string) ($image_asset['http_x_hcdn_cache_status'] ?? ''),
            'http_x_hcdn_request_id' => (string) ($image_asset['http_x_hcdn_request_id'] ?? ''),
            'body_starts_with_jpeg' => (bool) ($image_asset['body_starts_with_jpeg'] ?? false),
            'body_detected_mime'  => (string) ($image_asset['body_detected_mime'] ?? ''),
            'body_magic'          => (string) ($image_asset['body_magic'] ?? ''),
            'body_matches_local'  => (bool) ($image_asset['body_matches_local'] ?? false),
            'body_size_matches_local' => (bool) ($image_asset['body_size_matches_local'] ?? false),
            'body_magic_matches_local' => (bool) ($image_asset['body_magic_matches_local'] ?? false),
            'http_content_length_matches_local' => (bool) ($image_asset['http_content_length_matches_local'] ?? false),
            'first_12_bytes_hex'  => (string) ($image_asset['first_12_bytes_hex'] ?? ''),
            'sha1'                => (string) ($image_asset['sha1'] ?? ''),
            'media_url_error_summary' => ((int) ($err['error_subcode'] ?? 0) === 2207052 || stripos((string) ($err['message'] ?? ''), 'Only photo or video can be accepted as media type') !== false)
                ? 'Meta could not fetch or accept the public media URL.'
                : '',
            'http_diagnostic_summary' => sprintf(
                'strategy=%s head=%d get=%d type=%s length=%s jpeg_magic=%s body_match=%s',
                (string) ($image_asset['url_strategy'] ?? ''),
                (int) ($image_asset['http_head_status'] ?? 0),
                (int) ($image_asset['http_get_status'] ?? 0),
                (string) ($image_asset['http_content_type'] ?? ''),
                (string) ($image_asset['http_content_length'] ?? ''),
                !empty($image_asset['body_starts_with_jpeg']) ? 'yes' : 'no',
                !empty($image_asset['body_matches_local']) ? 'yes' : 'no'
            ),
        ], $extra);
    }

    /**
     * Wait until an Instagram media container is ready (FINISHED) before publishing.
     *
     * Meta can return "Media ID is not available" if we publish too quickly.
     *
     * @param string $creation_id
     * @param string $token
     * @param int $max_attempts
     * @return true|WP_Error
     */
    private function wait_for_container_ready($creation_id, $token, $max_attempts = 10) {
        $creation_id = (string) $creation_id;
        if (!$creation_id) return new WP_Error('instagram', 'Missing creation_id.');

        // Give Instagram a moment to start processing.
        sleep(2);

        for ($i = 1; $i <= (int)$max_attempts; $i++) {
            $endpoint = '/' . rawurlencode($creation_id) . '?fields=status_code';
            $r = $this->request('GET', $endpoint, $token, null);

            if (is_wp_error($r)) return $r;
            if ($r['code'] < 200 || $r['code'] >= 300) {
                $msg = $r['body_json']['error']['message'] ?? $r['body_raw'] ?? 'Unknown error';
                return new WP_Error('instagram', 'Could not check media container status (' . $r['code'] . '): ' . $msg);
            }

            $status = strtoupper((string)($r['body_json']['status_code'] ?? ''));
            if ($status === 'FINISHED') return true;
            if ($status === 'ERROR' || $status === 'FAILED') {
                return new WP_Error('instagram', 'Instagram reported container status: ' . ($status ?: 'ERROR'));
            }

            // Still processing.
            // Backoff a bit: 2,2,3,3,4,...
            $sleep = 2 + (int)floor(($i - 1) / 2);
            sleep(min(6, $sleep));
        }

        return new WP_Error('instagram', 'The media is still processing. Please try again in a few seconds.');
    }

    /**
     * Publish an image to Instagram feed (image only).
     *
     * Requires options:
     * - calliope_sp_ig_access_token
     * - calliope_sp_ig_user_id (Instagram Business Account ID)
     *
     * @param array $data
     * @return true|WP_Error
     */
    public function publish($data) {
        $token = (string) get_option('calliope_sp_ig_access_token');
        $ig_user_id = (string) get_option('calliope_sp_ig_user_id');

        if (!$token || !$ig_user_id) {
            return new WP_Error('cfg', 'Instagram is not connected.');
        }

        $image_b64 = $data['image_b64'] ?? '';
        $image_asset = $this->image_asset_from_b64($image_b64, false);
        if (is_wp_error($image_asset)) return $image_asset;
        $image_url = esc_url_raw((string) ($image_asset['url'] ?? ''));
        if ($image_url === '') {
            return new WP_Error('instagram_invalid_media_url', 'Instagram could not prepare a valid public image URL.');
        }

        $caption_parts = [];
        $text = trim((string) ($data['content'] ?? ''));
        if ($text) $caption_parts[] = $text;

        $link = trim((string) ($data['link'] ?? ''));
        if ($link) $caption_parts[] = $link;

        $hashtags = trim((string) ($data['hashtags'] ?? ''));
        if ($hashtags) $caption_parts[] = $hashtags;

        $caption = trim(implode("\n\n", $caption_parts));

        $container_payload = [
            'image_url' => $image_url,
            'caption'   => $caption,
        ];

        if (class_exists('Calliope_SP_Logger')) {
            Calliope_SP_Logger::log('instagram', 'info', wp_json_encode([
                'message'      => 'Sending image URL to Instagram media container.',
                'image_url'    => $image_url,
                'url_strategy' => (string) ($image_asset['url_strategy'] ?? ''),
                'local_path'   => (string) ($image_asset['local_path'] ?? ''),
                'mime'         => (string) ($image_asset['mime'] ?? ''),
                'width'        => (int) ($image_asset['width'] ?? 0),
                'height'       => (int) ($image_asset['height'] ?? 0),
                'filesize'     => (int) ($image_asset['filesize'] ?? 0),
                'first_12_bytes_hex' => (string) ($image_asset['first_12_bytes_hex'] ?? ''),
                'sha1'         => (string) ($image_asset['sha1'] ?? ''),
                'http_head_status' => (int) ($image_asset['http_head_status'] ?? 0),
                'http_get_status' => (int) ($image_asset['http_get_status'] ?? 0),
                'http_content_type' => (string) ($image_asset['http_content_type'] ?? ''),
                'http_content_length' => (string) ($image_asset['http_content_length'] ?? ''),
                'http_accept_ranges' => (string) ($image_asset['http_accept_ranges'] ?? ''),
                'http_cache_control' => (string) ($image_asset['http_cache_control'] ?? ''),
                'http_x_content_type_options' => (string) ($image_asset['http_x_content_type_options'] ?? ''),
                'http_server' => (string) ($image_asset['http_server'] ?? ''),
                'http_via' => (string) ($image_asset['http_via'] ?? ''),
                'http_cf_cache_status' => (string) ($image_asset['http_cf_cache_status'] ?? ''),
                'http_x_litespeed_cache' => (string) ($image_asset['http_x_litespeed_cache'] ?? ''),
                'http_x_hcdn_cache_status' => (string) ($image_asset['http_x_hcdn_cache_status'] ?? ''),
                'http_x_hcdn_request_id' => (string) ($image_asset['http_x_hcdn_request_id'] ?? ''),
                'body_starts_with_jpeg' => (bool) ($image_asset['body_starts_with_jpeg'] ?? false),
                'body_matches_local' => (bool) ($image_asset['body_matches_local'] ?? false),
                'body_size_matches_local' => (bool) ($image_asset['body_size_matches_local'] ?? false),
                'body_magic_matches_local' => (bool) ($image_asset['body_magic_matches_local'] ?? false),
                'http_content_length_matches_local' => (bool) ($image_asset['http_content_length_matches_local'] ?? false),
                'meta_request_body' => $container_payload,
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        }

        // 1) Create media container
        $resp = $this->request('POST', '/' . rawurlencode($ig_user_id) . '/media', $token, $container_payload);

        if (is_wp_error($resp)) return $resp;
        if ($resp['code'] < 200 || $resp['code'] >= 300) {
            $msg = $resp['body_json']['error']['message'] ?? $resp['body_raw'] ?? 'Unknown error';
            $err_sub = (int) ($resp['body_json']['error']['error_subcode'] ?? 0);
            $initial_error_data = $this->build_graph_error_data($resp, $image_asset, [
                'stage' => 'create_media_container',
            ]);

            if (class_exists('Calliope_SP_Logger')) {
                Calliope_SP_Logger::log('instagram', 'error', wp_json_encode(array_merge([
                    'message' => 'Meta media URL diagnostics: Instagram create container failed with structured details.',
                ], $initial_error_data), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
            }

            if ($this->is_aspect_ratio_not_supported_error($msg)) {
                $retry_asset = $this->image_asset_from_b64($image_b64, true);
                if (!is_wp_error($retry_asset) && !empty($retry_asset['url'])) {
                    if (class_exists('Calliope_SP_Logger')) {
                        Calliope_SP_Logger::log('instagram', 'warning', wp_json_encode([
                            'message' => 'Instagram rejected the original aspect ratio. Retrying with square cropped image.',
                            'graph_error_message' => (string) $msg,
                        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
                    }

                    $image_url = esc_url_raw((string) $retry_asset['url']);
                    $image_asset = $retry_asset;
                    $container_payload = [
                        'image_url' => $image_url,
                        'caption'   => $caption,
                    ];

                    if (class_exists('Calliope_SP_Logger')) {
                        Calliope_SP_Logger::log('instagram', 'info', wp_json_encode([
                            'message' => 'Instagram square retry media container payload (access token excluded).',
                            'meta_request_body' => $container_payload,
                            'url_strategy' => (string) ($image_asset['url_strategy'] ?? ''),
                            'http_diagnostic_summary' => sprintf(
                                'strategy=%s head=%d get=%d type=%s length=%s jpeg_magic=%s body_match=%s',
                                (string) ($image_asset['url_strategy'] ?? ''),
                                (int) ($image_asset['http_head_status'] ?? 0),
                                (int) ($image_asset['http_get_status'] ?? 0),
                                (string) ($image_asset['http_content_type'] ?? ''),
                                (string) ($image_asset['http_content_length'] ?? ''),
                                !empty($image_asset['body_starts_with_jpeg']) ? 'yes' : 'no',
                                !empty($image_asset['body_matches_local']) ? 'yes' : 'no'
                            ),
                        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
                    }

                    $resp = $this->request('POST', '/' . rawurlencode($ig_user_id) . '/media', $token, $container_payload);

                    if (is_wp_error($resp)) return $resp;
                    if ($resp['code'] >= 200 && $resp['code'] < 300) {
                        // Retry worked, continue with the new container.
                    } else {
                        $msg = $resp['body_json']['error']['message'] ?? $resp['body_raw'] ?? 'Unknown error';
                        $err_sub = (int) ($resp['body_json']['error']['error_subcode'] ?? 0);
                        $initial_error_data = $this->build_graph_error_data($resp, $retry_asset, [
                            'stage' => 'create_media_container_square_retry',
                        ]);
                    }
                }
            }

            if ($resp['code'] < 200 || $resp['code'] >= 300) {
                if (
                    $err_sub === 2207052 ||
                    stripos((string) $msg, 'Only photo or video can be accepted as media type') !== false
                ) {
                    return new WP_Error(
                        'instagram_invalid_media_url',
                        'Instagram could not use this image because the public media URL is not accessible or does not return a valid image. Please try another image or use Original format.',
                        $initial_error_data
                    );
                }

                if ($this->is_aspect_ratio_not_supported_error($msg)) {
                    return new WP_Error(
                        'instagram_unsupported_aspect_ratio',
                        'Instagram rejected the image aspect ratio even after the plugin prepared a compatible crop. Please try a square or 4:5 image.',
                        $initial_error_data
                    );
                }

                return new WP_Error('instagram', 'Could not create media container (' . $resp['code'] . '): ' . $msg, $initial_error_data);
            }
        }

        $creation_id = $resp['body_json']['id'] ?? '';
        if (!$creation_id) {
            return new WP_Error('instagram', 'Missing creation_id from Instagram response.', $this->build_graph_error_data($resp, $image_asset, ['stage' => 'missing_creation_id']));
        }

        // 1.5) Wait until container is ready
        $ready = $this->wait_for_container_ready($creation_id, $token, 10);
        if (is_wp_error($ready)) return $ready;

        // 2) Publish container (with a small retry for "not ready" responses)
        $last_err = '';
        for ($try = 1; $try <= 5; $try++) {
            $resp2 = $this->request('POST', '/' . rawurlencode($ig_user_id) . '/media_publish', $token, [
                'creation_id' => $creation_id,
            ]);

            if (is_wp_error($resp2)) return $resp2;
            if ($resp2['code'] >= 200 && $resp2['code'] < 300) {
                return true;
            }

            $err_code = (int)($resp2['body_json']['error']['code'] ?? 0);
            $err_sub  = (int)($resp2['body_json']['error']['error_subcode'] ?? 0);
            $last_err = $resp2['body_json']['error']['message'] ?? $resp2['body_raw'] ?? 'Unknown error';

            // 9007 / 2207027 => media not ready for publishing yet.
            if ($resp2['code'] === 400 && $err_code === 9007 && $err_sub === 2207027) {
                $ready2 = $this->wait_for_container_ready($creation_id, $token, 6);
                if (is_wp_error($ready2)) return $ready2;
                continue;
            }

            return new WP_Error('instagram', 'Could not publish media (' . $resp2['code'] . '): ' . $last_err, $this->build_graph_error_data($resp2, $image_asset, ['stage' => 'media_publish']));
        }

        return new WP_Error('instagram', 'Could not publish media: ' . ($last_err ?: 'Unknown error'));
    }

    /**
     * Test connection by fetching pages and attempting to find an Instagram Business account.
     *
     * @return true|WP_Error
     */
    public function test_connection() {
        $token = (string) get_option('calliope_sp_ig_access_token');
        if (!$token) return new WP_Error('cfg', 'Missing Instagram access token.');

        // Instagram Business Login tokens can be validated via graph.instagram.com
        $me_url = 'https://graph.instagram.com/me?fields=id,username&access_token=' . rawurlencode($token);
        $resp = wp_remote_get($me_url, ['timeout' => 60]);
        if (!is_wp_error($resp)) {
            $code = (int) wp_remote_retrieve_response_code($resp);
            $raw  = (string) wp_remote_retrieve_body($resp);
            if (class_exists('Calliope_SP_Logger')) Calliope_SP_Logger::log('instagram', ($code>=200 && $code<300) ? 'info' : 'error', 'HTTP ' . $code . ' | ' . mb_substr($raw, 0, 1200));

            $json = json_decode($raw, true);
            if ($code >= 200 && $code < 300 && is_array($json) && !empty($json['id'])) {
                update_option('calliope_sp_ig_user_id', sanitize_text_field((string)$json['id']));
                if (!empty($json['username'])) update_option('calliope_sp_ig_username', sanitize_text_field((string)$json['username']));
                return true;
            }
        }

        // Fallback: validate token against Graph API (some accounts return metadata only there).
        $fb_me_url = 'https://graph.instagram.com/v24.0/me?fields=id,name&access_token=' . rawurlencode($token);
        $resp2 = wp_remote_get($fb_me_url, ['timeout' => 60]);
        if (is_wp_error($resp2)) return $resp2;

        $code2 = (int) wp_remote_retrieve_response_code($resp2);
        $raw2  = (string) wp_remote_retrieve_body($resp2);
        if (class_exists('Calliope_SP_Logger')) Calliope_SP_Logger::log('instagram', ($code2>=200 && $code2<300) ? 'info' : 'error', 'HTTP ' . $code2 . ' | ' . mb_substr($raw2, 0, 1200));

        $json2 = json_decode($raw2, true);
        if ($code2 >= 200 && $code2 < 300 && is_array($json2) && !empty($json2['id'])) {
            return true;
        }

        $msg = is_array($json2) ? ($json2['error']['message'] ?? $raw2) : $raw2;
        return new WP_Error('instagram', 'Could not validate Instagram token: ' . $msg);
    }
}
