<?php

namespace OtomaticAi\Controllers;

use Exception;
use OtomaticAi\Models\Presets\Preset;
use OtomaticAi\Utils\Language;
use OtomaticAi\Vendors\Illuminate\Support\Arr;
use OtomaticAi\Vendors\Illuminate\Support\Str;
use OtomaticAi\Api\Haloscan\Client as HaloscanClient;

class GenerateTitlesController extends Controller
{
    public function generateTitles()
    {
        $this->verifyNonce();

        $this->validate([
            "style" => ["required", "string"],
            "language" => ["required", "string"],
            "keyword" => ["required", "string"],
            "length" => ["nullable", "numeric", "min:1"],
            "custom_instructions" => ["nullable", "string"],
        ]);

        $response = [];

        switch ($this->input('style')) {
            case "informative":
                $response = $this->generateTitlesFromPreset("generate_informative_titles");
                break;
            case "commercial":
                $response = $this->generateTitlesFromPreset("generate_commercial_titles");
                break;
            case "local":
                $response = $this->generateTitlesFromPreset("generate_local_titles");
                break;
            case "brands":
                $response = $this->generateTitlesFromPreset("generate_brands_titles");
                break;
            case "comparative":
                $response = $this->generateTitlesFromPreset("generate_comparative_titles");
                break;
            case "guide":
                $response = $this->generateTitlesFromPreset("generate_guide_titles");
                break;
            case "discover":
                $response = $this->generateTitlesFromPreset("generate_discover_titles");
                break;
        }

        $this->response($response);
    }

    public function generateSiloingTitles()
    {
        $this->verifyNonce();

        $this->validate([
            "style" => ["required", "string"],
            "language" => ["required", "string"],
            "keyword" => ["required", "string"],
            "custom_instructions" => ["nullable", "string"],
        ]);

        $response = [];

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

            // make payload
            $payload = [
                "request" => $this->input('keyword'),
                "language" => Language::find($this->input('language', 'en'))->value,
                "has_custom_instructions" => !empty($this->input('custom_instructions', "")),
                "custom_instructions" => $this->input('custom_instructions', ""),
            ];

            // run the preset
            $response = $preset->process($payload);

            // get the response content
            $items = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
            $response = $this->parseSiloingItems($items, $this->input("keyword"));
            $response = $this->generateTitlesFromPresetAndSiloing("generate_informative_titles", $response);
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        $this->response($response);
    }

    public function generateHaloscanTitles()
    {
        $this->verifyNonce();

        $this->validate([
            "type" => ["required", "string"],
            "style" => ["required", "string"],
            "language" => ["required", "string"],
            "keyword" => ["required", "string"],
            "length" => ["nullable", "numeric", "min:1"],
            "custom_instructions" => ["nullable", "string"],
        ]);

        $items = [];

        // call haloscan api to generate keywords
        try {
            $api = new HaloscanClient();

            $payload = [
                "keyword" => $this->input('keyword'),
                "lineCount" => $this->input('length', 50),
            ];

            $results = [];
            switch ($this->input("type", "match")) {
                case "similar":
                    $results = $api->keywordsSimilar($payload);
                    break;
                case "related":
                    $results = $api->keywordsRelated($payload);
                    break;
                case "questions":
                    $results = $api->keywordsQuestions($payload);
                    break;
                case "domain":
                    $payload = [
                        "input" => $this->input('keyword'),
                        "lineCount" => $this->input('length', 50),
                    ];
                    $results = $api->domainsPositions($payload);
                    break;
                case "match":
                default:
                    $results = $api->keywordsMatch($payload);
            }

            $results = Arr::get($results, "results", []);
            $items = Arr::pluck($results, 'keyword');
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        // verify if there are keywords
        if (!empty($items)) {

            // generate titles from keywords
            switch ($this->input('style')) {
                case "informative":
                    $preset = "generate_informative_titles";
                    break;
                case "commercial":
                    $preset = "generate_commercial_titles";
                    break;
                case "local":
                    $preset = "generate_local_titles";
                    break;
                case "brands":
                    $preset = "generate_brands_titles";
                    break;
                case "comparative":
                    $preset = "generate_comparative_titles";
                    break;
                case "guide":
                    $preset = "generate_guide_titles";
                    break;
                case "discover":
                    $preset = "generate_discover_titles";
                    break;
            }

            $items = $this->generateTitlesFromPresetAndKeywords($preset, $items);
        }

        $this->response($items);
    }

    public function generateKeywordsTitles()
    {
        $this->verifyNonce();

        $this->validate([
            "style" => ["required", "string"],
            "language" => ["required", "string"],
            "keywords" => ["required", "string"],
            "custom_instructions" => ["nullable", "string"],
        ]);


        $items = [];

        // split the keywords
        $keywords = explode("\n", $this->input('keywords'));
        $keywords = array_map(function ($keyword) {
            return Str::clean(trim($keyword));
        }, $keywords);
        $keywords = array_filter($keywords, function ($keyword) {
            return !empty($keyword);
        });

        // verify if there are keywords
        if (!empty($keywords)) {

            // generate titles from keywords
            switch ($this->input('style')) {
                case "informative":
                    $preset = "generate_informative_titles";
                    break;
                case "commercial":
                    $preset = "generate_commercial_titles";
                    break;
                case "local":
                    $preset = "generate_local_titles";
                    break;
                case "brands":
                    $preset = "generate_brands_titles";
                    break;
                case "comparative":
                    $preset = "generate_comparative_titles";
                    break;
                case "guide":
                    $preset = "generate_guide_titles";
                    break;
                case "discover":
                    $preset = "generate_discover_titles";
                    break;
            }

            $items = $this->generateTitlesFromPresetAndKeywords($preset, $keywords);

            $items = Arr::map($items, function ($item, $index) use ($keywords) {
                $arr = [
                    "title" => $item,
                    "meta" => []
                ];

                if (isset($keywords[$index])) {
                    $arr["meta"]["keyword"] = $keywords[$index];
                }

                return $arr;
            });
        }

        $this->response($items);
    }

    private function generateTitlesFromPreset(string $preset): array
    {
        try {
            // get the ai preset
            $preset = Preset::findFromAPI($preset);

            // make payload
            $payload = [
                "language" => Language::find($this->input('language', 'en'))->value,
                "keyword" => $this->input('keyword'),
                "length" => $this->input('length', 1),
                "has_custom_instructions" => !empty($this->input('custom_instructions', "")),
                "custom_instructions" => $this->input('custom_instructions', ""),
            ];

            // run the preset
            $response = $preset->process($payload);

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

            // add uppercase to the first letter
            $output = array_map(function ($str) {
                return ucfirst($str);
            }, $output);

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

        return [];
    }

    private function generateTitlesFromPresetAndKeywords(string $preset, array $keywords): array
    {
        try {
            // get the ai preset
            $preset = Preset::findFromAPI($preset);

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

            // 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");
                $value = Arr::get($items, 0);
                $value = Str::clean($value);
                if (!empty($value)) {
                    $output[$index] = $value;
                }
            }

            ksort($output);

            $output = array_values($output);

            // add uppercase to the first letter
            $output = array_map(function ($str) {
                return ucfirst($str);
            }, $output);

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

        return [];
    }

    private function generateTitlesFromPresetAndSiloing(string $preset, array $siloing): array
    {
        // get all titles from the siloing
        $titles = $this->getSiloingTitles($siloing);
        $keys = array_keys($titles);
        $titles = array_values($titles);

        try {

            // get the ai preset
            $preset = Preset::findFromAPI($preset);

            // make payloads
            $payloads = [];
            foreach ($titles as $title) {
                $payloads[] = [
                    "language" => Language::find($this->input('language', 'en'))->value,
                    "keyword" => $title,
                    "length" => 1,
                    "has_custom_instructions" => !empty($this->input('custom_instructions', "")),
                    "custom_instructions" => $this->input('custom_instructions', ""),
                ];
            }

            // 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");
                $value = Arr::get($items, 0);
                $value = Str::clean($value);
                if (!empty($value)) {
                    $output[$index] = $value;
                }
            }

            ksort($output);

            // replace the titles
            foreach ($output as $index => $newTitle) {
                $key = $keys[$index];
                $newTitle = ucfirst($newTitle);
                Arr::set($siloing, $key . ".title", $newTitle);
            }

            return $siloing;
        } 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 getSiloingTitles(array $siloing, string $key = null): array
    {
        $titles = [];

        if (strlen($key) > 0) {
            $key = $key . '.';
        }

        foreach ($siloing as $index => $item) {
            $newKey = $key . $index;
            $titles[$newKey] = Arr::get($item, "title");
            if (!empty(Arr::get($item, "children", []))) {
                $titles = array_merge($titles, $this->getSiloingTitles(Arr::get($item, "children"), $newKey . ".children"));
            }
        }

        return $titles;
    }
}
