<?php

namespace OtomaticAi\Controllers;

use Exception;
use OtomaticAi\Models\Presets\Preset;
use OtomaticAi\Utils\GoogleNews;
use OtomaticAi\Utils\Language;
use OtomaticAi\Utils\RobotsTxt;
use OtomaticAi\Utils\RSS\Reader;
use OtomaticAi\Vendors\Illuminate\Support\Arr;
use OtomaticAi\Vendors\Illuminate\Support\Str;
use OtomaticAi\Vendors\vipnytt\SitemapParser;
use OtomaticAi\Vendors\GuzzleHttp\Client;
use OtomaticAi\Vendors\GuzzleHttp\Pool;
use OtomaticAi\Vendors\GuzzleHttp\Psr7\Request;
use OtomaticAi\Vendors\GuzzleHttp\Psr7\Response;

class GenerateRequestController extends Controller
{
    public function __invoke()
    {
        $this->verifyNonce();

        $this->validate([
            "engine" => ["required", "string"],
            "style" => ["nullable", "string"],
            "query" => ["required", "string"],
            "length" => ["nullable", "numeric", "min:1"],
            "language" => ["required", "string"],
            "source_language" => ["nullable", "string"],
            "generate_siloing" => ["boolean"],
        ]);

        $response = [];

        switch ($this->input('engine')) {
            case "bulk":
                if ($this->input('generate_siloing', false)) {
                    $response["requests"] = $this->makeSiloingResponse();
                } else {
                    $response["requests"] = $this->makeBulkResponse();
                }
                break;
            case "sitemap":
                $response["requests"] = $this->makeSitemapResponse();
                break;
            case "url":
                $response["requests"] = $this->makeUrlResponse();
                break;
            case "news":
                $response["requests"] = $this->makeNewsResponse();
                break;
            case "rss":
                $response["requests"] = $this->makeRSSResponse();
                break;
        }

        $this->response($response);
    }

    public function getPosts()
    {
        $this->verifyNonce();
        $this->validate([
            "ids" => ["required", "array"],
        ]);

        $posts = get_posts([
            "post_type" => "any",
            "post__in" => $this->input("ids"),
            'posts_per_page' => -1,
        ]);

        $posts = array_map(function ($post) {
            return [
                "title" => $post->post_title,
                "meta" => [
                    "post_id" => $post->ID,
                    "url" => get_permalink($post->ID),
                ]
            ];
        }, $posts);

        $this->response($posts);
    }

    private function makeSiloingResponse(): array
    {
        try {
            // get the ai preset
            $preset = Preset::findFromAPI("generate_siloing");

            // make keywords
            $keywords = explode("\n", $this->input('query', ""));
            $keywords = array_map(function ($str) {
                return Str::clean($str);
            }, $keywords);
            $keywords = array_values(array_filter($keywords, function ($str) {
                return !empty($str);
            }));

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

            // make payloads
            $payloads = [];
            foreach ($keywords as $keyword) {
                $payloads[] = [
                    "language" => Language::find($this->input('language', 'en'))->value,
                    "style" => $this->input('style'),
                    "request" => $keyword,
                ];
            }

            // run the pool preset
            $responses = $preset->processPool($payloads);

            // get the response content
            $output = [];
            foreach ($responses as $index => $response) {
                $items = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
                $output[$index] = $this->parseSiloingItems($items, $keywords[$index]);
            }

            ksort($output);

            $output = Arr::flatten($output, 1);

            // add uppercase to the first letter
            $output = array_map(function ($item) {
                $item["title"] = ucfirst($item["title"]);
                return $item;
            }, $output);

            return $output;
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        return [];
    }


    private function parseSiloingItems(array $item, string $keyword): array
    {
        $output = [];

        if (Arr::isList($item)) {
            foreach ($item as $subItem) {
                $output = array_merge($output, $this->parseSiloingItems($subItem, $keyword));
            }
        } else {
            $title = Arr::get($item, "title");
            $title = Str::clean($title);

            if (!empty($title)) {
                $output[] = [
                    "title" => $title,
                    "meta" => [
                        "keyword" => $keyword
                    ],
                    "children" => empty(Arr::get($item, "children", [])) ? [] : $this->parseSiloingItems(Arr::get($item, "children", []), $keyword)
                ];
            }
        }

        return $output;
    }

    private function makeBulkResponse(): array
    {
        try {
            // get the ai preset
            $preset = Preset::findFromAPI("generate_titles");

            // make keywords
            $keywords = explode("\n", $this->input('query', ""));
            $keywords = array_map(function ($str) {
                return Str::clean($str);
            }, $keywords);
            $keywords = array_values(array_filter($keywords, function ($str) {
                return !empty($str);
            }));

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

            // make payloads
            $payloads = [];
            foreach ($keywords as $keyword) {
                $payloads[] = [
                    "language" => Language::find($this->input('language', 'en'))->value,
                    "length" => $this->input('length', 1),
                    "style" => $this->input('style'),
                    "request" => $keyword,
                ];
            }

            // run the pool preset
            $responses = $preset->processPool($payloads);

            // get the response content
            $output = [];
            foreach ($responses as $index => $response) {
                $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[] = [
                            "title" => $item,
                            "meta" => [
                                "keyword" => $keywords[$index]
                            ]
                        ];
                    }
                }
            }

            ksort($output);

            // add uppercase to the first letter
            $output = array_map(function ($item) {
                $item["title"] = ucfirst($item["title"]);
                return $item;
            }, $output);

            return $output;
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        return [];
    }

    private function makeUrlResponse(): array
    {
        try {
            // make urls
            $urls = explode("\n", $this->input('query', ""));
            $urls = array_map(function ($str) {
                return Str::clean($str);
            }, $urls);
            $urls = array_values(array_filter($urls, function ($str) {
                return !empty($str);
            }));

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

            // get urls titles
            $titles = $this->getUrlsTitles($urls);

            // rewrite titles
            if (!empty($titles)) {

                // get the openai preset
                $preset = Preset::findFromAPI("rewrite_title");

                // make payloads
                $payloads = [];
                foreach ($titles as $title) {
                    $payloads[] = [
                        "language" => Language::find($this->input('language', 'en'))->value,
                        "request" => $title["title"],
                    ];
                }

                // run the pool preset
                $responses = $preset->processPool($payloads);

                // get the response content
                foreach ($responses as $index => $response) {
                    $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
                    $item = Arr::get($response, "value");
                    $item = Str::clean($item);
                    if (!empty($item) && isset($titles[$index])) {
                        $titles[$index]["title"] = ucfirst($item);
                    }
                }

                ksort($titles);
            }

            return $titles;
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        return [];
    }

    private function makeNewsResponse(): array
    {
        try {
            $items = GoogleNews::search($this->input('query'), Language::find($this->input('source_language', $this->input('language', 'en'))));

            $items = Arr::map($items, function ($item) {
                $title = $item["title"];
                $url = $item["url"];
                $guid = $item["url"];
                $guid = str_replace(["http://", "https://"], "", $guid);

                return [
                    "title" => $title,
                    "meta" => [
                        "url" => $url,
                        "guid" => $guid,
                    ]
                ];
            });

            $titles = array_slice($items, 0, $this->input('length', 1));

            // rewrite titles
            if (!empty($titles)) {

                // get the openai preset
                $preset = Preset::findFromAPI("rewrite_title");

                // make payloads
                $payloads = [];
                foreach ($titles as $title) {
                    $payloads[] = [
                        "language" => Language::find($this->input('language', 'en'))->value,
                        "request" => $title["title"],
                    ];
                }

                // run the pool preset
                $responses = $preset->processPool($payloads);

                // get the response content
                foreach ($responses as $index => $response) {
                    $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
                    $item = Arr::get($response, "value");
                    $item = Str::clean($item);
                    if (!empty($item) && isset($titles[$index])) {
                        $titles[$index]["title"] = ucfirst($item);
                    }
                }

                ksort($titles);
            }

            return $titles;
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        return [];
    }

    private function makeRSSResponse(): array
    {
        try {
            $rss = Reader::load($this->input("query"));

            $items = [];
            foreach ($rss->items as $item) {

                $url = (string) $item->url;
                $title = (string) $item->title;
                $guid = (string) $item->guid;
                if (strlen($guid) < 0) {
                    $guid = str_replace(["http://", "https://"], "", $url);
                }
                $guid = str_replace(["http://", "https://"], "", $guid);

                $items[] = [
                    "title" => $title,
                    "meta" => [
                        "url" => $url,
                        "guid" => $guid,
                    ]
                ];
            }

            $titles = array_slice($items, 0, $this->input('length', 1));

            // rewrite titles
            if (!empty($titles)) {

                // get the openai preset
                $preset = Preset::findFromAPI("rewrite_title");

                // make payloads
                $payloads = [];
                foreach ($titles as $title) {
                    $payloads[] = [
                        "language" => Language::find($this->input('language', 'en'))->value,
                        "request" => $title["title"],
                    ];
                }

                // run the pool preset
                $responses = $preset->processPool($payloads);

                // get the response content
                foreach ($responses as $index => $response) {
                    $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
                    $item = Arr::get($response, "value");
                    $item = Str::clean($item);
                    if (!empty($item) && isset($titles[$index])) {
                        $titles[$index]["title"] = ucfirst($item);
                    }
                }

                ksort($titles);
            }

            return $titles;
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        return [];
    }

    private function makeSitemapResponse(): array
    {
        try {

            // get sitemap
            $parser = new SitemapParser();
            $parser->parse($this->input('query', ""));
            $urls = array_keys($parser->getURLs());

            // get urls titles
            $titles = $this->getUrlsTitles($urls);

            // rewrite titles
            if (!empty($titles)) {

                // get the openai preset
                $preset = Preset::findFromAPI("rewrite_title");

                // make payloads
                $payloads = [];
                foreach ($titles as $title) {
                    $payloads[] = [
                        "language" => Language::find($this->input('language', 'en'))->value,
                        "request" => $title["title"],
                    ];
                }

                // run the pool preset
                $responses = $preset->processPool($payloads);

                // get the response content
                foreach ($responses as $index => $response) {
                    $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
                    $item = Arr::get($response, "value");
                    $item = Str::clean($item);
                    if (!empty($item) && isset($titles[$index])) {
                        $titles[$index]["title"] = ucfirst($item);
                    }
                }

                ksort($titles);
            }
            return $titles;
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        return [];
    }

    private function getUrlsTitles(array $urls): array
    {
        $output = [];

        // create the client
        $client = new Client(
            [
                "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',
                ]
            ]
        );

        // make the requests
        $requests = [];
        foreach ($urls as $url) {
            if (!$this->isDisabledByRobotsTxt($url)) {
                $requests[] = new Request(
                    'GET',
                    $url,
                );
            }
        }

        // create the pool
        $pool = new Pool($client, $requests, [
            'concurrency' => 20,
            'fulfilled' => function (Response $response, $index) use (&$output, $urls) {
                $response = $response->getBody()->getContents();
                // h1
                $title = preg_match('/<h1[^>]*>(.*?)<\/h1>/ims', $response, $matches) ? $matches[1] : null;
                if (!empty($urls[$index]) && !empty($title)) {
                    $output[$index] = [
                        "title" => mb_convert_encoding($title, 'UTF-8', 'UTF-8'),
                        "meta" => [
                            "url" => mb_convert_encoding($urls[$index], 'UTF-8', 'UTF-8'),
                        ]
                    ];
                } else {
                    // title
                    $title = preg_match('/<title[^>]*>(.*?)<\/title>/ims', $response, $matches) ? $matches[1] : null;
                    if (!empty($urls[$index]) && !empty($title)) {
                        $output[$index] = [
                            "title" => mb_convert_encoding($title, 'UTF-8', 'UTF-8'),
                            "meta" => [
                                "url" => mb_convert_encoding($urls[$index], 'UTF-8', 'UTF-8'),
                            ]
                        ];
                    }
                }
            },
            'rejected' => function (Exception $reason, $index) {},
        ]);

        // call the pool
        $promise = $pool->promise();
        $promise->wait();

        // sort and get only values
        ksort($output);
        return array_values($output);
    }

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