<?php

namespace SEOAIC;

use Exception;

class SEOAIC_BRAND_VISIBILITY {
    private $seoaic;
    private string $endpointGetPrompts = 'api/brand-tracking/list';
    private string $endpointCreatePrompts = 'api/brand-tracking/create';
    private string $endpointUpdatePrompts = 'api/brand-tracking/update';
    private string $endpointRemovePrompts = 'api/brand-tracking/remove';
    private string $endpointPromptsDetails = 'api/brand-tracking/result';
    private string $endpointAnalyzePrompts = 'api/brand-tracking/scan';
    private string $endpointConfig         = 'api/brand-tracking/config';
    private string $endpointUpdateConfig       = 'api/brand-tracking/update-config';
    private string $endpointManualCreate    = 'api/brand-tracking/manual-create';
    private string $endpointGetCompetitors    = 'api/brand-tracking/competitors';
    private string $endpointUpdateCompetitors   = 'api/brand-tracking/update-competitors';
    private string $endpointProvidersList       = 'api/allowed-brand-tracking-providers/list';
    private string $endpointProvidersRequest    = 'api/allowed-brand-tracking-providers/request';


    public function __construct( $seoaic ) {
        $this->seoaic = $seoaic;

        add_action( 'wp_ajax_seoaic_get_prompts', [ $this, 'getPromptsAjax' ] );
        add_action( 'wp_ajax_seoaic_create_prompts', [ $this, 'createPromptsAjax' ] );
        add_action( 'wp_ajax_seoaic_create_manual_prompts', [ $this, 'createManualPromptsAjax' ] );

        add_action( 'wp_ajax_seoaic_update_prompts', [ $this, 'updatePromptsAjax' ] );
        add_action( 'wp_ajax_seoaic_remove_prompts', [ $this, 'removePromptsAjax' ] );
        add_action( 'wp_ajax_seoaic_analyze_prompts', [ $this, 'analyzePromptsAjax' ] );

        add_action( 'wp_ajax_seoaic_update_competitors', [ $this, 'updateCompetitorsAjax' ] );
        add_action( 'wp_ajax_seoaic_remove_competitors', [ $this, 'removeCompetitorsAjax' ] );

        add_action( 'wp_ajax_seoaic_request_providers', [ $this, 'requestProvidersAjax' ] );
    }

    public function getProvidersList() {
        $providers = $this->seoaic->curl->initWithReturn($this->endpointProvidersList, [], true, true);

        if (empty($providers['data']) || !is_array($providers['data'])) {
            return [];
        }

        usort($providers['data'], function($a, $b) {
            return strcasecmp($a['provider'], $b['provider']);
        });

        return $providers['data'];
    }


    public function requestProvidersAjax() {
        $request = wp_unslash( $_REQUEST );

        try {
            $response = $this->seoaic->curl->initWithReturn( $this->endpointProvidersRequest, $request, true, true );
            SEOAICAjaxResponse::success()->addFields($response)->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error( 'Failed: ' . $e->getMessage() )->wpSend();
        }
    }

    public function getPrompts( array $args = [] ) {
        $prompts = $this->getAllPrompts();
        $prompts_details = $this->getAllPromptsDetails();

        foreach ($prompts as $key => $prompt) {
            $raw = $prompts_details[$prompt['id']] ?? [];
            $prompts[$key]['details'] = array_change_key_case(
                array_column($raw, null, 'source'),
                CASE_LOWER
            );
        }

        $prompts = $this->filterPromptsBySearch($prompts, $args['search'] ?? '');
        $prompts = $this->filterPromptsByDate($prompts, $args['created_date_from'] ?? '', $args['created_date_to'] ?? '');
        $prompts = $this->sortPrompts($prompts, $args['orderby'] ?? 'date', $args['order'] ?? 'desc');

        return $prompts;
    }

    public function getConfig() {
        return $this->seoaic->curl->initWithReturn($this->endpointConfig, [], true, true);
    }

    public function getPromptsDetails() {
        try {
            $response = $this->seoaic->curl->initWithReturn($this->endpointPromptsDetails, [], true, true);

            if (empty($response['data']) || !is_array($response['data'])) {
                return [];
            }

            return array_combine(
                array_column($response['data'], 'id'),
                array_column($response['data'], 'results')
            );
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error('Failed to get prompts: ' . $e->getMessage())->wpSend();
        }
    }

    public function analyzePromptsAjax() {
        $request = wp_unslash( $_REQUEST );

        if( !empty( $request['ids'] ) ) {
            $request['ids'] = explode( ',', $request['ids']);
        }

        try {
            $response = $this->seoaic->curl->initWithReturn( $this->endpointAnalyzePrompts, $request, true, true );
            SEOAICAjaxResponse::success()->addFields($response)->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error( 'Failed to get prompts: ' . $e->getMessage() )->wpSend();
        }
    }

    public function getPromptsAjax() {
        try {
            $response = $this->seoaic->curl->initWithReturn( $this->endpointGetPrompts, [], true, true );
            $this->assertApiSuccess( $response, 'Failed to get prompts' );

            SEOAICAjaxResponse::success()->addFields( [ 'data' => $response['data'] ?? [] ] )->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error( 'Failed to get prompts: ' . $e->getMessage() )->wpSend();
        }
    }

    public function createPromptsAjax() {
        global $SEOAIC_OPTIONS;

        try {
            $request = wp_unslash( $_REQUEST );
            $this->validateRequestData( $request, [ 'keywords' ] );

            if( !empty ( $SEOAIC_OPTIONS['seoaic_industry'] ) ){
                $request['companyIndustry'] = $SEOAIC_OPTIONS['seoaic_industry'];
            }

            $request['location']        = $this->seoaic->multilang->get_location_code_by_name( $request['location'] );
            $request['language']        = $this->seoaic->multilang->get_language_code_by_name( $request['language'] );

            $response = $this->seoaic->curl->initWithReturn( $this->endpointCreatePrompts, $request, true, true );

            SEOAICAjaxResponse::success()->addFields( $response )->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error( 'Failed to create prompts: ' . $e->getMessage() )->wpSend();
        }
    }

    public function createManualPromptsAjax() {
        global $SEOAIC_OPTIONS;

        try {
            $request = wp_unslash( $_REQUEST );
            $this->validateRequestData( $request, [ 'prompts' ] );

            if( !empty ( $SEOAIC_OPTIONS['seoaic_industry'] ) ){
                $request['companyIndustry'] = $SEOAIC_OPTIONS['seoaic_industry'];
            }

            $request['location']        = $this->seoaic->multilang->get_location_code_by_name( $request['location'] );
            $request['language']        = $this->seoaic->multilang->get_language_code_by_name( $request['language'] );

            $response = $this->seoaic->curl->initWithReturn( $this->endpointManualCreate, $request, true, true );

            SEOAICAjaxResponse::success()->addFields( $response )->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error( 'Failed to create prompts: ' . $e->getMessage() )->wpSend();
        }
    }

    public function updatePromptsAjax() {
        try {
            $request = wp_unslash( $_REQUEST );
            $this->validateRequestData( $request, [ 'bulk' ] );

            $bulk = $request['bulk'];
            $response = $this->seoaic->curl->initWithReturn( $this->endpointUpdatePrompts, $bulk, true, true );

            $this->assertApiSuccess( $response, 'Failed to update prompts' );
            SEOAICAjaxResponse::success()->wpSend();

        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error( 'Failed to update prompts: ' . $e->getMessage() )->wpSend();
        }
    }

    public function removePromptsAjax() {
        $request = wp_unslash( $_REQUEST );

        if( !empty( $request['item_ids'] ) ) {
            $request['ids'] = explode( ',', $request['item_ids']);
        }

        try {
            $this->validateRequestData( $request, [ 'ids' ] );
            $response = $this->seoaic->curl->initWithReturn( $this->endpointRemovePrompts, $request, true, true );

            SEOAICAjaxResponse::success('Prompts deleted')->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error( 'Failed to delete prompts: ' . $e->getMessage() )->wpSend();
        }
    }

    public function getCompetitors() {
        try {
            return $this->seoaic->curl->initWithReturn($this->endpointGetCompetitors, [], true, true);
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error('Failed to get competitors: ' . $e->getMessage())->wpSend();
        }
    }

    public function updateCompetitorsAjax() {
        $request = wp_unslash( $_REQUEST );

        try {
            $incomingCompetitors = $request['competitors'] ?? [];
            $incomingBrands = $request['brands'] ?? [];

            $request['brand_tracking_competitors'] = array_unique( $incomingCompetitors );
            $request['brand_tracking_main_branding_words'] = array_unique( $incomingBrands );

            $response = $this->seoaic->curl->initWithReturn( $this->endpointUpdateConfig, $request, true, true );
            SEOAICAjaxResponse::success()->addFields( $response )->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error('Failed to update competitors: ' . $e->getMessage())->wpSend();
        }
    }

    public function removeCompetitorsAjax() {
        $request = wp_unslash( $_REQUEST );

        try {
            $config = $this->getConfig();
            $competitors = $config['data']['brand_tracking_competitors'] ?? [];
            $this->validateRequestData( $request, [ 'competitors' ] );

            $incomingCompetitors = $request['competitors'] ?? [];
            $request['competitors'] = array_values( array_unique( array_diff( $competitors, $incomingCompetitors ) ) );

            $response = $this->seoaic->curl->initWithReturn( $this->endpointUpdateCompetitors, $request, true, true );
            SEOAICAjaxResponse::success()->addFields( $response )->wpSend();
        } catch ( Exception $e ) {
            SEOAICAjaxResponse::error('Failed to update competitors: ' . $e->getMessage())->wpSend();
        }
    }

    private function updateHistory() {
        global $SEOAIC_OPTIONS;

        $option_key    = 'seoaic_brand_visibility_history';
        $transient_key = 'seoaic_brand_visibility_lock';

        if (get_transient($transient_key)) {
            return $SEOAIC_OPTIONS[$option_key] ?? get_option($option_key, []);
        }

        $history = $SEOAIC_OPTIONS[$option_key] ?? get_option($option_key, []);

        if (!is_array($history)) {
            $history = [];
        }

        $resultData  = $this->seoaic->curl->initWithReturn($this->endpointPromptsDetails, [], true, true);
        $promptsData = $this->seoaic->curl->initWithReturn($this->endpointGetPrompts, [], true, true);

        if (
            empty($resultData['data']) || !is_array( $resultData['data'] ) ||
            empty($promptsData['data']) || !is_array( $promptsData['data'] )
        ) {
            return $history;
        }

        $results = array_combine(
            array_column($resultData['data'], 'id'),
            array_column($resultData['data'], 'results')
        );
        $prompts = array_combine(
            array_column($promptsData['data'], 'id'),
            array_column($promptsData['data'], 'last_scan_time')
        );

        $activePromptIds = array_map('strval', array_keys($prompts));

        foreach ($history as $source => &$entries) {
            if (!is_array($entries)) {
                unset($history[$source]);
                continue;
            }
            $entries = array_values(array_filter($entries, function ($entry) use ($activePromptIds) {
                return isset($entry['id']) && in_array((string)$entry['id'], $activePromptIds, true);
            }));
            if (empty($entries)) {
                unset($history[$source]);
            }
        }

        unset($entries);

        foreach ($results as $promptId => $providers) {
            $scanTime = $prompts[$promptId] ?? null;
            if (!$scanTime) continue;

            foreach ($providers as $providerData) {
                $source = $providerData['source'] ?? null;
                if (!$source) continue;

                $mentioned = !empty($providerData['mentioned']);

                if (!isset($history[$source])) {
                    $history[$source] = [];
                }

                $exists = false;
                foreach ($history[$source] as $entry) {
                    if ($entry['id'] == $promptId && $entry['date'] == $scanTime) {
                        $exists = true;
                        break;
                    }
                }

                if (!$exists) {
                    $history[$source][] = [
                        'id'        => $promptId,
                        'date'      => $scanTime,
                        'mentioned' => $mentioned
                    ];
                }
            }
        }

        update_option($option_key, $history);
        set_transient($transient_key, 1, 60 * MINUTE_IN_SECONDS);

        return $history;
    }

    public function getHistory(){

        global $SEOAIC_OPTIONS;
        $this->updateHistory();

        $option_key = 'seoaic_brand_visibility_history';
        return $SEOAIC_OPTIONS[$option_key] ?? get_option($option_key, []);
    }
	private function getAllPrompts() {
		$result = $this->seoaic->curl->initWithReturn($this->endpointGetPrompts, [], true, true);

		if (!is_array($result) || empty($result['data']) || !is_array($result['data'])) {
			return [];
		}

		return $result['data'];
	}

    private function getAllPromptsDetails() {
        $response = $this->seoaic->curl->initWithReturn($this->endpointPromptsDetails, [], true, true);

	    if (!is_array($response) || empty($response['data']) || !is_array($response['data'])) {
		    return [];
	    }

        return array_combine(
            array_column($response['data'], 'id'),
            array_column($response['data'], 'results')
        );
    }
    private function filterPromptsBySearch(array $prompts, string $search): array {
        if (!$search) return $prompts;

        $searchWords = preg_split('/\s+/', mb_strtolower(trim($search)));

        return array_filter($prompts, function($item) use ($searchWords) {
            $haystack = implode(' ', [
                mb_strtolower($item['prompt'] ?? ''),
                mb_strtolower($item['original_keyword'] ?? ''),
            ]);

            foreach ($searchWords as $word) {
                if (mb_strpos($haystack, $word) === false) {
                    return false;
                }
            }

            return true;
        });
    }

    private function filterPromptsByDate(array $prompts, string $from, string $to) : array {
        if (!$from && !$to) return $prompts;
        return array_filter($prompts, function($item) use ($from, $to) {
            $created = strtotime($item['created_at']);
            if ($from && $created < strtotime($from)) return false;
            if ($to && $created > strtotime($to)) return false;
            return true;
        });
    }

    private function sortPrompts(array $prompts, string $orderBy, string $order): array {
        if (!$orderBy) return $prompts;

        $desc = (strtolower($order) === 'desc');

        usort($prompts, function($a, $b) use ($orderBy, $desc) {
            $valA = $valB = null;

            if ($orderBy === 'date') {
                $valA = isset($a['created_at']) ? (strtotime($a['created_at']) ?: 0) : 0;
                $valB = isset($b['created_at']) ? (strtotime($b['created_at']) ?: 0) : 0;

            } elseif (strpos($orderBy, 'provider-') === 0) {
                $provider = substr($orderBy, strlen('provider-'));
                $provider = strtolower($provider);

                $valA = $a['details'][$provider]['mentioned'] ?? null;
                $valB = $b['details'][$provider]['mentioned'] ?? null;

                $valA = is_bool($valA) ? (int)$valA : $valA;
                $valB = is_bool($valB) ? (int)$valB : $valB;

            } else {
                $valA = $a[$orderBy] ?? null;
                $valB = $b[$orderBy] ?? null;
            }

            $aNull = ($valA === null);
            $bNull = ($valB === null);

            if ($aNull && $bNull) return 0;
            if ($aNull) return 1;
            if ($bNull) return -1;

            if (is_numeric($valA) && is_numeric($valB)) {
                $cmp = $valA <=> $valB;
            } else {
                $aStr = is_string($valA) ? mb_strtolower($valA) : (string)$valA;
                $bStr = is_string($valB) ? mb_strtolower($valB) : (string)$valB;
                $cmp  = $aStr <=> $bStr;
            }

            return $desc ? -$cmp : $cmp;
        });

        return $prompts;
    }

    private function validateRequestData( $request, array $required_fields = [] ): void {
        if ( ! is_array( $request ) || empty( $request ) ) {
            SEOAICAjaxResponse::error('Invalid request payload.' )->wpSend();
        }
        foreach ( $required_fields as $field ) {
            if ( ! isset( $request[ $field ] ) || $request[ $field ] === '' ) {
                SEOAICAjaxResponse::error( 'Missing or empty required field: ' . $field )->wpSend();
            }
        }
    }

    private function assertApiSuccess( $response, $defaultErrorMsg ) {
        if ( empty( $response ) || empty( $response['status'] ) || $response['status'] !== 'success' ) {
            throw new Exception( ! empty( $response['message'] ) ? $response['message'] : $defaultErrorMsg );
        }
    }

    public static function mdToHTML($text) {
        if ($text === '' || $text === null) return '';

        $text = self::preformatLooseText($text);
        $text = str_replace(["\r\n", "\r"], "\n", $text);
        $text = esc_html($text);

        $lines   = preg_split('/\n/', $text);
        $out     = [];
        $bufP    = [];
        $list    = null;
        $olStart = 1;
        $inFence = false;
        $bufCode = [];

        $flushParagraph = function() use (&$bufP, &$out) {
            if (!$bufP) return;
            $para = trim(implode(' ', array_map('trim', $bufP)));
            $bufP = [];
            if ($para === '') return;
            $para = self::inlineFormat($para);
            $out[] = '<p>' . $para . '</p>';
        };

        $closeList = function() use (&$list, &$olStart, &$out) {
            if ($list === 'ul') $out[] = '</ul>';
            if ($list === 'ol') $out[] = '</ol>';
            $list = null;
            $olStart = 1;
        };

        $flushFence = function() use (&$bufCode, &$out) {
            $code = implode("\n", $bufCode);
            $bufCode = [];
            $out[] = "<pre><code>{$code}</code></pre>";
        };

        foreach ($lines as $rawLine) {
            $line = rtrim($rawLine);

            if (preg_match('/^```/', $line)) {
                if ($inFence) {
                    $inFence = false;
                    $flushFence();
                } else {
                    $flushParagraph();
                    $closeList();
                    $inFence = true;
                    $bufCode = [];
                }
                continue;
            }
            if ($inFence) {
                $bufCode[] = $line;
                continue;
            }

            if (trim($line) === '') {
                $flushParagraph();
                $closeList();
                continue;
            }

            if (preg_match('/^\s*---\s*$/', $line)) {
                $flushParagraph();
                $closeList();
                $out[] = '<hr />';
                continue;
            }

            if (preg_match('/^(#{1,6})\s+(.+)$/u', trim($line), $m)) {
                $flushParagraph();
                $closeList();
                $level = strlen($m[1]);
                $textH = self::inlineFormat(trim($m[2]));
                $out[] = "<h{$level}>{$textH}</h{$level}>";
                continue;
            }

            if (preg_match('/^[-\*\+]\s+(.+)/u', $line, $m)) {
                $flushParagraph();
                if ($list === null) { $list = 'ul'; $out[] = '<ul>'; }
                if ($list !== 'ul')  { $closeList(); $list = 'ul'; $out[] = '<ul>'; }
                $li = self::inlineFormat($m[1]);
                $out[] = "<li>{$li}</li>";
                continue;
            }

            if (preg_match('/^(\d+)\s*[\.\)]\s+(.+)/u', $line, $m)) {
                $flushParagraph();
                $num = (int) $m[1];
                if ($list === null) {
                    $list = 'ol';
                    $olStart = $num;
                    $out[] = $olStart > 1 ? "<ol start=\"{$olStart}\">" : '<ol>';
                } elseif ($list !== 'ol') {
                    $closeList();
                    $list = 'ol';
                    $olStart = $num;
                    $out[] = $olStart > 1 ? "<ol start=\"{$olStart}\">" : '<ol>';
                }
                $li = self::inlineFormat($m[2]);
                $out[] = "<li>{$li}</li>";
                continue;
            }

            if ($list !== null) {
                $closeList();
            }
            $bufP[] = $line;
        }

        $flushParagraph();
        $closeList();
        if ($inFence) {
            $flushFence();
        }

        return implode("\n", $out);
    }

    private static function preformatLooseText(string $text): string {
        if ($text === '') return '';

        $text = str_replace(["\r\n", "\r"], "\n", $text);
        $text = preg_replace('/[ \t]+/u', ' ', $text);
        $text = preg_replace('/\n{3,}/u', "\n\n", $text);
        $text = preg_replace('~\s+(#{1,6}\s+)~u', "\n$1", $text);
        $text = preg_replace('~\s+(-\s+)~u', "\n$1", $text);
        $text = preg_replace('~\s+(\*\s+)~u', "\n$1", $text);
        $text = preg_replace('~\s+(\+\s+)~u', "\n$1", $text);
        $text = preg_replace('~\s+(\d+\s*[\.\)]\s+)~u', "\n$1", $text);
        $text = preg_replace('~:\s+(?=(?:[-\*\+]\s+|\d+\s*[\.\)]\s+))~u', ":\n", $text);
        $text = preg_replace('~\s+(---)\s+~u', "\n$1\n", $text);

        return $text;
    }

    private static function inlineFormat(string $s): string {
        $s = preg_replace_callback(
            '/\(\s*(?:([a-z]+:\/\/)?([^\s\)]+))\s*\)\s*\[([^\]]+)\]/iu',
            function($m) {
                $proto = $m[1] ?: '';
                $rest  = $m[2];
                $text  = $m[3];
                $href  = $proto !== '' ? ($proto . $rest) : ('https://' . $rest);
                return '<a href="'.esc_url($href).'" target="_blank" rel="noopener noreferrer">'.esc_html($text).'</a>';
            },
            $s
        );

        $s = preg_replace_callback(
            '/\[(.*?)\]\(\s*(https?:\/\/[^\s\)]+)\s*\)/iu',
            function($m) {
                return '<a href="'.esc_url($m[2]).'" target="_blank" rel="noopener noreferrer">'.esc_html($m[1]).'</a>';
            },
            $s
        );

        $s = preg_replace_callback(
            '~(?<!["\'=<>])\bhttps?://[^\s<>()]+~iu',
            function($m) {
                $raw = $m[0];
                $trimmed = rtrim($raw, '.,);:!?»”\'"');
                $tail = substr($raw, strlen($trimmed));
                return '<a href="'.esc_url($trimmed).'" target="_blank" rel="noopener noreferrer">'.esc_html($trimmed).'</a>'.$tail;
            },
            $s
        );

        $s = preg_replace('/`([^`]+)`/u', '<code>$1</code>', $s);
        $s = preg_replace('/\*\*(.+?)\*\*/us', '<strong>$1</strong>', $s);
        $s = preg_replace('/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/us', '<em>$1</em>', $s);

        return $s;
    }
}