<?php

namespace OtomaticAi\Utils;

use DOMDocument;
use Exception;
use OtomaticAi\Api\OtomaticAi\Client as OtomaticAiClient;
use OtomaticAi\Models\Persona;
use OtomaticAi\Models\Presets\Preset;
use OtomaticAi\Utils\GoogleNews;
use OtomaticAi\Vendors\andreskrey\Readability\Configuration;
use OtomaticAi\Vendors\andreskrey\Readability\Readability;
use OtomaticAi\Vendors\Illuminate\Support\Arr;
use OtomaticAi\Vendors\Illuminate\Support\Str;
use WP_Query;

class ContentData
{
    /**
     * Get the content of the provided url
     *
     * @param string $url
     * @return string|null
     * @throws Exception
     */
    public static function getUrlContent(string $url): ?string
    {
        if (empty($url)) {
            throw new Exception("The url is empty.");
        }

        // verify robots txt
        if (self::isDisabledByRobotsTxt($url)) {
            throw new Exception("The url is disabled by robots.txt.");
        }

        // scrap the meta.url content
        $response = wp_remote_get($url, [
            "headers" => [
                "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
            ]
        ]);

        // quit if the content is not usable
        if (is_wp_error($response) || empty($body = Arr::get($response, "body")) || strlen($body) < 200) {
            throw new Exception("Failed to get the url content.");
        }

        $content = null;
        try {
            // remove boilerplate frop body
            $configuration = new Configuration([
                "articleByLine" => true,
                "summonCthulhu" => true,
                "normalizeEntities" => true,
            ]);
            $readability = new Readability($configuration);
            $readability->parse($body);
            $content = $readability->getContent();

            $doc = new DOMDocument();
            if ($doc->loadHTML('<?xml encoding="UTF-8">' . $content)) {
                return trim($doc->textContent);
            }
            return $content;
        } catch (Exception $e) {
        }

        return null;
    }

    /**
     * Get the captions of the provided YouTube video
     *
     * @param string $videoId
     * @param Language $language
     * @return string|null
     * @throws Exception
     */
    public static function getYoutubeVideoCaptions(string $videoId, Language $language): ?string
    {
        if (empty($videoId)) {
            throw new Exception("The YouTube video ID is empty.");
        }

        $captions = null;
        try {
            $api = new OtomaticAiClient;
            $result = $api->youtubeVideo($videoId, $language->key);
            return Arr::get($result, "captions");
        } catch (Exception $e) {
            throw new Exception("Failed to get the YouTube video captions.");
        }

        if (empty($captions)) {
            throw new Exception("The YouTube video captions are empty.");
        }

        return null;
    }

    /**
     * Get external data
     * 
     * @param string $title
     * @param Language $language
     * @return array
     */
    public static function getExternalData(string $title, Language $language): array
    {
        $data = [];

        // get google results
        $googleResults = self::getGoogleResults($title, $language);

        // organic results
        $organicResults = Arr::get($googleResults, "organic_results", []);
        if (count($organicResults) > 0) {
            $organicResults = array_map(function ($item) {
                return [
                    "link" => Arr::get($item, "link"),
                    "title" => Arr::get($item, "title", ""),
                    "content" => trim(Arr::get($item, "title", "") . "\n" . Arr::get($item, "snippet", "")),
                ];
            }, $organicResults);
            $data = array_merge($data, $organicResults);
        }

        return $data;
    }

    /**
     * Get internal data
     * 
     * @param string $title
     * @param Language $language
     * @param int|null $postId
     * @return array
     */
    public static function getInternalData(string $title, Language $language, int $postId = null): array
    {
        $data = [];

        // get related posts
        $relatedPosts = self::getRelatedPosts($title, $language, $postId);
        if (!empty($relatedPosts)) {
            $relatedPosts = array_map(function ($post) {
                return [
                    "content" => get_the_title($post),
                    "link" => get_permalink($post),
                ];
            }, $relatedPosts);
            $data = array_merge($data, $relatedPosts);
        }

        return $data;
    }

    /**
     * Get the persona system message form selected, or generate one
     *
     * @param Persona|null $persona
     * @param string $title
     * @param Language $language
     * @param string|null $customInstructions
     * @return string
     * @throws Exception
     */
    public static function getPersona(Persona $persona = null, string $title, Language $language, string $customInstructions = null): string
    {
        if ($persona) {
            return $persona->instructions;
        }

        return "";
    }

    /**
     * Get the writing style
     *
     * @param string $writingStyle
     * @return string
     */
    public static function getWritingStyle(string $writingStyle = null): string
    {
        switch ($writingStyle) {
            case "narrative":
                return "Narratif";
                break;
            case "descriptive":
                return "Descriptif";
                break;
            case "objective":
                return "Objectif";
                break;
            case "poetic":
                return "Poétique";
                break;
            case "persuasive":
                return "Persuasif";
                break;
            case "expository":
                return "Expositoire";
                break;
            case "creative":
                return "Créatif";
                break;
            case "technical":
                return "Technique";
                break;
            case "entertaining":
                return "Divertissant";
                break;
            case "subjective":
                return "Subjectif";
                break;
            case "funny":
                return "Drôle";
                break;
            case "sales-oriented":
                return "Vendeur";
                break;
            case "formal":
                return "Soutenu";
                break;
            case "ironic":
                return "Ironique";
                break;
            case "inspiring":
                return "Inspirant";
                break;
            case "educative":
                return "Éducatif";
                break;
            case "motivating":
                return "Motivant";
                break;
            case "informal":
                return "Informel";
                break;
            case "provocative":
                return "Provocateur";
                break;
            case "emotional":
                return "Émotionnel";
                break;
            case "critical":
                return "Critique";
                break;
            case "sarcastic":
                return "Sarcastique";
                break;
            case "imaginative":
                return "Imaginatif";
                break;
            case "pragmatic":
                return "Pragmatique";
                break;
            case "argumentative":
                return "Argumentatif";
                break;
            case "factual":
                return "Factuel";
                break;
        }

        return "";
    }

    /**
     * Get keywords
     * 
     * @param string $title
     * @param Language $language
     * @return array
     */
    public static function getKeywords(string $title, Language $language): array
    {
        try {
            // get the openai preset
            $preset = Preset::findFromAPI("generate_seo_keywords");


            // run the preset
            $response = $preset->process([
                "language" => $language->value,
                "title" => $title,
            ]);

            // get the response content
            $output = [];
            $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
            $items = Arr::get($response, "values");
            if (!empty($items)) {
                $items = array_map(function ($str) {
                    return Str::clean($str);
                }, $items);
                $items = array_values(array_filter($items, function ($str) {
                    return !empty($str);
                }));
                foreach ($items as $item) {
                    $output[] = $item;
                }
            }

            return $output;
        } catch (Exception $e) {
        }

        return [];
    }

    /**
     * Get google news links
     * 
     * @param string $title
     * @param Language $language
     * @return array
     */
    private static function getGoogleNewsLinks(string $title, Language $language): array
    {
        // retrieve google news rss
        try {
            return GoogleNews::search($title, $language);
        } catch (Exception $e) {
            return [];
        }
    }

    /**
     * Get google results
     * 
     * @param string $title
     * @param Language $language
     * @return array
     */
    private static function getGoogleResults(string $title, Language $language): array
    {
        try {
            $api = new OtomaticAiClient;
            return $api->googleSearch($title, $language->key);
        } catch (Exception $e) {
            return [];
        }
    }

    /**
     * Get related posts
     * 
     * @param string $title
     * @param Language $language
     * @param int|null $postId
     * @return array
     */
    private static function getRelatedPosts(string $title, Language $language, int $postId = null): array
    {
        $search = Str::lower($title);

        try {
            $preset = Preset::findFromAPI("get_main_keyword");

            $response = $preset->process([
                "language" => $language->value,
                "request" => $search,
            ]);

            // get the response content
            $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
            $content = Arr::get($response, "value");
            $content = Str::clean($content);

            $search = Str::lower($content);
        } catch (Exception $e) {
        }

        if (empty($search)) {
            return [];
        }

        $args = [
            "s" => $search,
        ];

        // if the pll is installed, get only the posts in the default language
        if (function_exists("pll_the_languages") && function_exists("pll_default_language")) {
            $defaultLanguage = pll_default_language();
            $languages = pll_the_languages(['raw' => 1]);

            if (isset($languages[$defaultLanguage], $languages[$defaultLanguage]["id"])) {
                $args['tax_query'] = [
                    [
                        'taxonomy' => 'language',
                        'field' => 'term_id',
                        'terms' => $languages[$defaultLanguage]["id"]
                    ]
                ];
            }
        }

        if (!empty($postId)) {
            $args["post__not_in"] = [$postId];
        }

        $query = new WP_Query($args);

        return $query->get_posts();
    }


    /**
     * Generate a persona
     *
     * @param string $title
     * @param Language $language
     * @return string
     */
    private static function generatePersona(string $title, Language $language): string
    {
        try {
            // get the persona preset
            $preset = Preset::findFromAPI("generate_persona");

            // run the preset
            $response = $preset->process([
                "language" => $language->value,
                "request" => $title,
            ]);

            // get the response content
            $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
            $content = Arr::get($response, "value");

            return Str::clean($content);
        } catch (Exception $e) {
            return "";
        }
    }

    /**
     * Determine if the scrap is disabled by the robots.txt
     *
     * @param string $url
     * @return boolean
     */
    private static function isDisabledByRobotsTxt(string $url)
    {
        $robots = RobotsTxt::fromUrl($url);
        return !empty($robots->getRules("otomaticAI"));
    }
}
