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

class SailWP_Copilot_REST {
    const NAMESPACE_V1 = 'sailwp-copilot/v1';

    public function __construct() {
        add_action('rest_api_init', [$this, 'register_routes']);
    }

    public function register_routes() {
        register_rest_route(self::NAMESPACE_V1, '/chat', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_chat'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);

        register_rest_route(self::NAMESPACE_V1, '/cloud-session', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_cloud_session'],
            'permission_callback' => [$this, 'permission_admin'],
        ]);

        register_rest_route(self::NAMESPACE_V1, '/status', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_status'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);

        register_rest_route(self::NAMESPACE_V1, '/redeem-coupon', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_redeem_coupon'],
            'permission_callback' => [$this, 'permission_admin'],
        ]);

        // Finalize a previously-started async design job: pulls the rendered
        // HTML from the proxy, creates the preview draft post, returns the
        // usual preview_id / view_url / edit_url. Called by the rail once
        // /design-status reports done.
        register_rest_route(self::NAMESPACE_V1, '/finalize-design', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_finalize_design'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);

        // Same-origin polling proxy: the rail hits this endpoint every few
        // seconds while a design job is running. We forward to the AI
        // proxy server-side so the browser never has to cross origins
        // (no CORS preflights, no flaky cross-domain headers killing the
        // experience when a kid is waiting for their cosmos).
        register_rest_route(self::NAMESPACE_V1, '/design-status', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_design_status'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);

        // Direct apply: bypass the Claude round-trip when the user clicks
        // "Apply this design" on the preview banner. The preview_id and
        // intent are unambiguous so there's no reason to send "Yes, apply
        // this design." through the chat — that adds two model turns and
        // a tool-confirmation chip the user has to wade through.
        register_rest_route(self::NAMESPACE_V1, '/apply-design', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_apply_design'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);

        // Inline edits from the frontend overlay: text-content tweaks and
        // section reordering on _swcp_designed pages. The client sends the
        // post_id plus the updated full design HTML; we re-wrap it in the
        // wp:html block markers and persist via wp_update_post (auto-rev).
        register_rest_route(self::NAMESPACE_V1, '/update-design', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_update_design'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);

        // Brand-profile editor (Settings → Brand profile). Auto-extracted
        // from the host site; overrides are sparse and win on merge.
        register_rest_route(self::NAMESPACE_V1, '/brand-profile', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_brand_profile_get'],
            'permission_callback' => [$this, 'permission_admin'],
        ]);
        register_rest_route(self::NAMESPACE_V1, '/brand-profile/refresh', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_brand_profile_refresh'],
            'permission_callback' => [$this, 'permission_admin'],
        ]);
        register_rest_route(self::NAMESPACE_V1, '/brand-profile/overrides', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_brand_profile_overrides_save'],
            'permission_callback' => [$this, 'permission_admin'],
        ]);
        register_rest_route(self::NAMESPACE_V1, '/brand-profile/overrides/reset', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_brand_profile_overrides_reset'],
            'permission_callback' => [$this, 'permission_admin'],
        ]);

        // Chat history (server-side persistence on ai.sailwp.com).
        // Browser hits these same-origin endpoints; we forward to the
        // proxy with the install's license key so the browser never
        // touches cross-origin or the raw license.
        register_rest_route(self::NAMESPACE_V1, '/chat-history/upsert', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_chat_history_upsert'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);
        register_rest_route(self::NAMESPACE_V1, '/chat-history/list', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_chat_history_list'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);
        register_rest_route(self::NAMESPACE_V1, '/chat-history/get', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_chat_history_get'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);
        register_rest_route(self::NAMESPACE_V1, '/chat-history/delete', [
            'methods'             => 'DELETE',
            'callback'            => [$this, 'handle_chat_history_delete'],
            'permission_callback' => [$this, 'permission_edit'],
        ]);

        // One-shot migration: rewrites every existing _swcp_designed=1 page
        // into the self-contained envelope. Idempotent — safe to re-run.
        register_rest_route(self::NAMESPACE_V1, '/migrate-self-contained', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_migrate_self_contained'],
            'permission_callback' => [$this, 'permission_admin'],
        ]);
    }

    public function handle_migrate_self_contained() {
        if (!function_exists('swcp_migrate_self_contained')) {
            return new WP_Error('not_loaded', 'self-contained helper is not loaded', ['status' => 500]);
        }
        return rest_ensure_response(swcp_migrate_self_contained());
    }

    private function chat_history_proxy_request($path, $method, $payload = null, $query = []) {
        $license = SailWP_Copilot_Plugin::get_license_key();
        if (!$license) {
            return new WP_Error('no_license', 'This site is not connected yet.', ['status' => 400]);
        }
        $url = SailWP_Copilot_Plugin::get_ai_proxy_url() . $path;
        if (!empty($query)) {
            $url = add_query_arg($query, $url);
        }
        $args = [
            'method'  => strtoupper($method),
            'timeout' => 15,
            'headers' => [
                'Content-Type'   => 'application/json',
                'X-License-Key'  => $license,
                'X-Site-API-Key' => SailWP_Copilot_Plugin::get_site_api_key() ?: '',
            ],
        ];
        if ($payload !== null) $args['body'] = wp_json_encode($payload);
        $resp = wp_remote_request($url, $args);
        if (is_wp_error($resp)) {
            return new WP_Error('proxy_unreachable', $resp->get_error_message(), ['status' => 502]);
        }
        $code = wp_remote_retrieve_response_code($resp);
        $body = json_decode(wp_remote_retrieve_body($resp), true);
        if (!is_array($body)) {
            return new WP_Error('proxy_bad_response', 'Chat-history service returned non-JSON.', ['status' => 502]);
        }
        $r = rest_ensure_response($body);
        $r->set_status($code ?: 200);
        return $r;
    }

    public function handle_chat_history_upsert(WP_REST_Request $request) {
        $chat_id  = (string) $request->get_param('chat_id');
        $title    = (string) $request->get_param('title');
        $messages = $request->get_param('messages');
        if (!$chat_id || !is_array($messages)) {
            return new WP_Error('bad_request', 'chat_id and messages[] are required', ['status' => 400]);
        }
        return $this->chat_history_proxy_request('/api/v1/chat-history/upsert', 'POST', [
            'chat_id'  => $chat_id,
            'title'    => $title,
            'site_url' => home_url(),
            'messages' => $messages,
        ]);
    }

    public function handle_chat_history_list(WP_REST_Request $request) {
        $limit = (int) $request->get_param('limit');
        if ($limit <= 0) $limit = 30;
        return $this->chat_history_proxy_request('/api/v1/chat-history/list', 'GET', null, ['limit' => $limit]);
    }

    public function handle_chat_history_get(WP_REST_Request $request) {
        $chat_id = (string) $request->get_param('chat_id');
        if (!$chat_id) return new WP_Error('bad_request', 'chat_id is required', ['status' => 400]);
        return $this->chat_history_proxy_request('/api/v1/chat-history/get', 'GET', null, ['chat_id' => $chat_id]);
    }

    public function handle_chat_history_delete(WP_REST_Request $request) {
        $chat_id = (string) $request->get_param('chat_id');
        if (!$chat_id) return new WP_Error('bad_request', 'chat_id is required', ['status' => 400]);
        return $this->chat_history_proxy_request('/api/v1/chat-history/delete', 'DELETE', null, ['chat_id' => $chat_id]);
    }

    public function handle_brand_profile_get() {
        if (!class_exists('SailWP_Copilot_Brand_Profile')) {
            return new WP_Error('not_available', 'Brand profile component is not loaded.', ['status' => 500]);
        }
        return rest_ensure_response([
            'ok'        => true,
            'profile'   => SailWP_Copilot_Brand_Profile::get(),
            'overrides' => SailWP_Copilot_Brand_Profile::get_overrides(),
        ]);
    }

    public function handle_brand_profile_refresh() {
        if (!class_exists('SailWP_Copilot_Brand_Profile')) {
            return new WP_Error('not_available', 'Brand profile component is not loaded.', ['status' => 500]);
        }
        $profile = SailWP_Copilot_Brand_Profile::get(true);
        return rest_ensure_response(['ok' => true, 'profile' => $profile]);
    }

    public function handle_brand_profile_overrides_save(WP_REST_Request $request) {
        if (!class_exists('SailWP_Copilot_Brand_Profile')) {
            return new WP_Error('not_available', 'Brand profile component is not loaded.', ['status' => 500]);
        }
        $overrides = $request->get_param('overrides');
        if (!is_array($overrides)) $overrides = [];
        $clean = SailWP_Copilot_Brand_Profile::save_overrides($overrides);
        return rest_ensure_response([
            'ok'        => true,
            'overrides' => $clean,
            'profile'   => SailWP_Copilot_Brand_Profile::get(),
        ]);
    }

    public function handle_brand_profile_overrides_reset() {
        if (!class_exists('SailWP_Copilot_Brand_Profile')) {
            return new WP_Error('not_available', 'Brand profile component is not loaded.', ['status' => 500]);
        }
        SailWP_Copilot_Brand_Profile::reset_overrides();
        return rest_ensure_response([
            'ok'        => true,
            'profile'   => SailWP_Copilot_Brand_Profile::get(),
        ]);
    }

    public function handle_finalize_design(WP_REST_Request $request) {
        $job_id      = (string) $request->get_param('job_id');
        $original_id = (int) $request->get_param('original_id');
        if (!function_exists('swcp_finalize_design')) {
            require_once SAILWP_COPILOT_DIR . 'includes/tools/design-page-preview.php';
        }
        $result = swcp_finalize_design($job_id, $original_id);
        return rest_ensure_response($result);
    }

    public function handle_apply_design(WP_REST_Request $request) {
        $preview_id = (int) $request->get_param('preview_id');
        if (!$preview_id) {
            return new WP_Error('missing_preview_id', 'preview_id is required', ['status' => 400]);
        }
        if (!function_exists('swcp_tool_apply_page_design')) {
            require_once SAILWP_COPILOT_DIR . 'includes/tools/apply-page-design.php';
        }
        if (!function_exists('swcp_check_builder_compat')) {
            require_once SAILWP_COPILOT_DIR . 'includes/tools/helpers.php';
        }
        $result = swcp_tool_apply_page_design(['preview_id' => $preview_id], null);
        return rest_ensure_response($result);
    }

    public function handle_update_design(WP_REST_Request $request) {
        $post_id  = (int) $request->get_param('post_id');
        $new_html = (string) $request->get_param('html');
        if (!$post_id) {
            return new WP_Error('missing_post_id', 'post_id is required', ['status' => 400]);
        }
        if (!current_user_can('edit_post', $post_id)) {
            return new WP_Error('not_allowed', 'You cannot edit this page', ['status' => 403]);
        }
        $post = get_post($post_id);
        if (!$post instanceof WP_Post) {
            return new WP_Error('not_found', 'Page not found', ['status' => 404]);
        }
        if (!get_post_meta($post_id, '_swcp_designed', true)) {
            return new WP_Error('not_designed', 'Inline edits are only allowed on Co-pilot designed pages', ['status' => 400]);
        }

        // Strip dangerous executable content the same way kses-friendly
        // raw HTML editing does. The Co-pilot's design is wp:html so we
        // accept full markup, but we re-affirm: no <script>, no inline
        // event handlers. This is defence-in-depth — only editors can
        // hit this endpoint anyway, but a compromised editor session
        // shouldn't be able to plant XSS via inline-edit.
        $clean_html = $this->sanitize_design_html($new_html);

        // Wrap in self-contained envelope so the page survives plugin
        // deactivation (CSS-bypass lives inline in the saved content).
        $new_content = function_exists('swcp_wrap_self_contained')
            ? swcp_wrap_self_contained($clean_html)
            : "<!-- wp:html -->\n" . $clean_html . "\n<!-- /wp:html -->";
        $update = wp_update_post([
            'ID'           => $post_id,
            'post_content' => $new_content,
        ], true);
        if (is_wp_error($update)) {
            return new WP_Error('save_failed', $update->get_error_message(), ['status' => 500]);
        }
        clean_post_cache($post_id);
        return rest_ensure_response([
            'ok'      => true,
            'post_id' => $post_id,
            'message' => 'Saved.',
        ]);
    }

    private function sanitize_design_html($html) {
        // Strip <script> tags entirely (defence in depth — only logged-in
        // editors with edit_posts capability can reach this, but a
        // hijacked session shouldn't be able to inject XSS).
        $html = preg_replace('#<script\b[^>]*>.*?</script\s*>#is', '', (string) $html);
        // Strip inline event handlers (onclick=, onload=, ...).
        $html = preg_replace('#\s+on[a-z]+\s*=\s*"[^"]*"#i', '', $html);
        $html = preg_replace("#\\s+on[a-z]+\\s*=\\s*'[^']*'#i", '', $html);
        // Strip javascript: URLs.
        $html = preg_replace('#(href|src)\s*=\s*"\s*javascript:[^"]*"#i', '$1="#"', $html);
        $html = preg_replace("#(href|src)\\s*=\\s*'\\s*javascript:[^']*'#i", '$1="#"', $html);
        return $html;
    }

    public function handle_design_status(WP_REST_Request $request) {
        $job_id = (string) $request->get_param('job_id');
        if ($job_id === '') {
            return new WP_Error('missing_job_id', 'job_id is required', ['status' => 400]);
        }
        $url = SailWP_Copilot_Plugin::get_ai_proxy_url() . '/copilot/design-page-status?job_id=' . rawurlencode($job_id);
        $response = wp_remote_get($url, [
            'timeout' => 10,
            'headers' => array_filter([
                'X-License-Key'  => SailWP_Copilot_Plugin::get_license_key() ?: null,
                'X-Site-API-Key' => SailWP_Copilot_Plugin::get_site_api_key() ?: null,
            ]),
        ]);
        if (is_wp_error($response)) {
            return new WP_Error('proxy_unreachable', $response->get_error_message(), ['status' => 502]);
        }
        $code = wp_remote_retrieve_response_code($response);
        $body = json_decode(wp_remote_retrieve_body($response), true);
        if (!is_array($body)) {
            return new WP_Error('proxy_bad_response', 'Status endpoint returned non-JSON', ['status' => 502]);
        }
        // Pass the proxy's payload back verbatim — including the 404
        // "job_not_found" which the rail handles as a graceful give-up.
        $resp = rest_ensure_response($body);
        $resp->set_status($code ?: 200);
        return $resp;
    }

    public function permission_admin() {
        return current_user_can('manage_options');
    }

    public function permission_edit() {
        return current_user_can('edit_posts');
    }

    public function handle_chat(WP_REST_Request $request) {
        $messages = $request->get_param('messages');
        if (!is_array($messages) || empty($messages)) {
            return new WP_Error('invalid_messages', 'messages must be a non-empty array', ['status' => 400]);
        }
        $context = $request->get_param('context');
        $opts = is_array($context) ? ['context' => $context] : [];

        $result = SailWP_Copilot_AI_Client::chat($messages, $opts);
        if (is_wp_error($result)) return $result;
        return rest_ensure_response($result);
    }

    public function handle_cloud_session() {
        if (!SailWP_Copilot_Plugin::is_connected_to_cloud()) {
            return new WP_Error('not_connected', 'Plugin is not connected to SailWP Cloud', ['status' => 400]);
        }

        $url = SailWP_Copilot_Plugin::get_ai_proxy_url() . '/v1/cloud-session';
        $response = wp_remote_post($url, [
            'timeout' => 15,
            'headers' => [
                'Content-Type'   => 'application/json',
                'X-License-Key'  => SailWP_Copilot_Plugin::get_license_key(),
                'X-Site-API-Key' => SailWP_Copilot_Plugin::get_site_api_key(),
            ],
            'body' => wp_json_encode([
                'site' => home_url(),
            ]),
        ]);

        if (is_wp_error($response)) return $response;
        $code = wp_remote_retrieve_response_code($response);
        $payload = json_decode(wp_remote_retrieve_body($response), true);
        if ($code >= 400 || !is_array($payload)) {
            return new WP_Error('cloud_session_failed', 'Could not create bridge token', ['status' => $code ?: 500]);
        }
        return rest_ensure_response([
            'token'   => $payload['token']   ?? null,
            'app_url' => $payload['app_url'] ?? SailWP_Copilot_Plugin::get_cloud_url(),
        ]);
    }

    public function handle_redeem_coupon(WP_REST_Request $request) {
        $code = strtoupper(trim((string) $request->get_param('code')));
        if (!$code) {
            return new WP_Error('code_required', 'Please enter a coupon code.', ['status' => 400]);
        }
        $license = SailWP_Copilot_Plugin::get_license_key();
        if (!$license) {
            return new WP_Error('no_license', 'No license available on this site yet. Please reload and try again.', ['status' => 400]);
        }
        $proxy = SailWP_Copilot_Plugin::get_ai_proxy_url();
        $resp = wp_remote_post($proxy . '/api/v1/redeem-coupon', [
            'timeout' => 15,
            'headers' => [
                'Content-Type'  => 'application/json',
                'X-License-Key' => $license,
            ],
            'body' => wp_json_encode(['code' => $code]),
        ]);
        if (is_wp_error($resp)) return $resp;
        $code_http = wp_remote_retrieve_response_code($resp);
        $body = json_decode(wp_remote_retrieve_body($resp), true);
        if (!is_array($body)) {
            return new WP_Error('upstream_error', 'The redemption service did not respond. Try again.', ['status' => 502]);
        }
        // Bust the is_pro() transient cache so the hero CTA flips immediately.
        delete_transient('swcp_plan_' . md5($license));
        if ($code_http >= 400 || empty($body['ok'])) {
            $msg = $body['message'] ?? ($body['error'] ?? 'Could not redeem.');
            return new WP_REST_Response(['ok' => false, 'message' => $msg, 'error' => $body['error'] ?? null], $code_http >= 400 ? $code_http : 400);
        }
        return rest_ensure_response($body);
    }

    public function handle_status() {
        return rest_ensure_response([
            'version'           => SAILWP_COPILOT_VERSION,
            'connected'         => SailWP_Copilot_Plugin::is_connected_to_cloud(),
            'workspace_id'      => SailWP_Copilot_Plugin::get_workspace_id() ?: null,
            'site_id'           => SailWP_Copilot_Plugin::get_site_id() ?: null,
            'ai_proxy_url'      => SailWP_Copilot_Plugin::get_ai_proxy_url(),
            'cloud_url'         => SailWP_Copilot_Plugin::get_cloud_url(),
        ]);
    }
}
