<?php

namespace OtomaticAi\Controllers;

use Exception;
use OtomaticAi\Api\OpenAi\Client as OpenAiClient;
use OtomaticAi\Api\StabilityAi\Client as StabilityAiClient;
use OtomaticAi\Api\Ideogram\Client as IdeogramClient;
use OtomaticAi\Api\Flux\Client as FluxClient;
use OtomaticAi\Api\XAi\Client as XAiClient;
use OtomaticAi\Api\Gemini\Client as GeminiClient;
use OtomaticAi\Content\Image\DallE;
use OtomaticAi\Content\Image\Flux;
use OtomaticAi\Content\Image\Gemini;
use OtomaticAi\Content\Image\Grok;
use OtomaticAi\Content\Image\Ideogram;
use OtomaticAi\Content\Image\StableDiffusion;
use OtomaticAi\Models\Persona;
use OtomaticAi\Models\Presets\Preset;
use OtomaticAi\Models\WP\User;
use OtomaticAi\Utils\Image;
use OtomaticAi\Utils\Language;
use OtomaticAi\Utils\Settings;
use OtomaticAi\Vendors\Illuminate\Support\Arr;
use OtomaticAi\Vendors\Illuminate\Support\Str;
use OtomaticAi\Vendors\Illuminate\Validation\Rule;
use Simple_Local_Avatars;

class PersonaController extends Controller
{
    public function index()
    {
        $this->verifyNonce();

        $personas = Persona::whereHas('user')->with("user")->get();
        $this->response($personas);
    }

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

        $this->validate([
            "limit" => ["nullable", "integer", "min:1"],
        ]);

        $personas = Persona::whereHas('user')->with("user")->latest()->limit($this->input("limit", 5))->get();

        $this->response($personas);
    }

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

        $this->validate([
            "id" => ["required"],
        ]);

        $persona = Persona::find($this->input("id"));

        if ($persona === null) {
            $this->response(["message" => "An error occurred", "error" => "Unable to find the persona #" . $this->input("id") . "."], 503);
        }

        $persona->load(["user"]);

        $this->response($persona);
    }

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

        $this->validateLanguage();
        $this->validateUser();
        $this->validateProfil();
        $this->validateBiography();
        $this->validateAvatar();

        // create or get the user
        if ($this->input("mode") === "create") {
            $userId = wp_create_user($this->input("username"), $this->input("password"), $this->input("email", ""));
            if (is_wp_error($userId)) {
                $this->response(["message" => "An error occurred", "error" => "Unable to create the user.<br>" . $userId->get_error_message()], 503);
            }
            // set role
            if (!empty($this->input("role"))) {
                $user = get_user_by("ID", $userId);
                $user->set_role($this->input("role"));
            }

            // get model
            $user = User::find($userId);
        } else if ($this->input("mode") === "attach") {
            $user = User::find($this->input("user_id"));
        } else {
            $this->emptyResponse();
        }

        // update user_meta
        update_user_meta($user->ID, "first_name", $this->input("first_name"));
        update_user_meta($user->ID, "last_name", $this->input("last_name"));
        update_user_meta($user->ID, "description", $this->input("description"));

        // attach the avatar
        if (class_exists("Simple_Local_Avatars") && !empty($this->input("avatar.attachment_id"))) {
            try {
                $sla = new Simple_Local_Avatars();
                $sla->assign_new_user_avatar($this->input("avatar.attachment_id"), $user->ID);
            } catch (Exception $e) {
            }
        }

        // create the persona
        $persona = new Persona;
        $persona->user()->associate($user);

        $persona->language = $this->input("language");
        $persona->age = $this->input("age");
        $persona->job = $this->input("job");
        $persona->writing_style = $this->input("writing_style");
        $persona->save();

        if ($persona->save()) {
            $this->emptyResponse();
        } else {
            $this->response(["message" => "An error occurred", "error" => "Unable to create the persona."], 503);
        }
    }

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

        $this->validate([
            "persona" => ["required", "integer"],
        ]);

        $persona = Persona::find($this->input('persona'));
        if ($persona) {
            $persona->delete();
        }

        $this->emptyResponse();
    }

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

        $this->validate([
            "id" => ["required"],
        ]);
        $this->validateProfil();
        $this->validateBiography();
        $this->validateCustomInstructions();

        // get the persona
        $persona = Persona::find($this->input("id"));

        if ($persona === null) {
            $this->response(["message" => "An error occurred", "error" => "Unable to find the persona #" . $this->input("id") . "."], 503);
        }

        $persona->load(["user"]);

        // update user_meta
        update_user_meta($persona->user->ID, "first_name", $this->input("first_name"));
        update_user_meta($persona->user->ID, "last_name", $this->input("last_name"));
        update_user_meta($persona->user->ID, "description", $this->input("description"));

        // update persona
        $persona->age = $this->input("age");
        $persona->job = $this->input("job");
        $persona->writing_style = $this->input("writing_style");
        $persona->custom_instructions = $this->input("custom_instructions");
        $persona->save();

        $this->emptyResponse();
    }

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

        $this->validate([
            "id" => ["required"],
            "model" => ["required", "string"],
            "custom_instructions" => ["nullable", "string"],
        ]);

        $this->validateSimpleLocalAvatar();

        // get the persona
        $persona = Persona::find($this->input("id"));

        if ($persona === null) {
            $this->response(["message" => "An error occurred", "error" => "Unable to find the persona #" . $this->input("id") . "."], 503);
        }

        if (!class_exists("Simple_Local_Avatars")) {
            $this->response(["message" => "An error occurred", "error" => "Simple Local Avatars plugin is required."], 503);
        }

        $persona->load(["user"]);

        // attach the avatar
        try {
            $image = $this->makeAvatar($this->input("model", "flux_1_dev"), trim($this->input("custom_instructions", "")), $persona->language, $persona->user_first_name, $persona->age, $persona->job);

            if (!empty($image)) {
                $attachmentId = $image->save('user avatar');

                if ($attachmentId !== null) {
                    $sla = new Simple_Local_Avatars();
                    $sla->assign_new_user_avatar($attachmentId, $persona->user->ID);
                }
            }
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        $this->response($persona->fresh());
    }

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

        $this->validateLanguage();

        $this->emptyResponse();
    }

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

        $this->validateUser();

        $this->emptyResponse();
    }

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

        $this->validateProfil();
        $this->validateBiography();

        $this->emptyResponse();
    }

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

        $this->validateBiography();

        $this->emptyResponse();
    }

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

        $this->validateLanguage();
        $this->validateProfil();

        try {

            $preset = Preset::findFromAPI("generate_user_biography");
            $response = $preset->process([
                "language" => Language::find($this->input("language", "en"))->value,
                "name" => $this->input("first_name", ''),
                "age" => $this->input("age", 30),
                "job" => $this->input("job", ''),
            ]);

            // 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);

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

        $this->emptyResponse();
    }

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

        $this->validateLanguage();
        $this->validateProfil();
        $this->validateSimpleLocalAvatar();
        $this->validateAPIKeys();

        try {
            $image = $this->makeAvatar($this->input("avatar_model", "flux_1_dev"), $this->input("avatar_custom_instructions", ""), $this->input('language'), $this->input("first_name", ''), $this->input("age", 30), $this->input("job", ''));

            if (!empty($image)) {

                $attachmentId = $image->save('user avatar');

                if ($attachmentId !== null) {
                    $this->response(
                        [
                            "avatar" => [
                                "attachment_id" => $attachmentId,
                                "url" => wp_get_attachment_url($attachmentId)
                            ]
                        ],
                    );
                }
            }
        } catch (Exception $e) {
            $this->response(["message" => "An error occurred", "error" => $e->getMessage()], 503);
        }

        $this->emptyResponse();
    }

    /**
     * Make a persona avatar
     *
     * @param string $model
     * @param string $language
     * @param string $name
     * @param int|null $age
     * @param string|null $job
     * @return Image|null
     * @throws Exception
     */
    private function makeAvatar(string $model, string $custom_instructions, string $language, string $name, ?int $age = null, ?string $job = null): ?Image
    {
        // get the preset for generate the stable diffusion prompt
        $preset = Preset::findFromAPI("generate_persona_avatar_prompt");

        // process the preset
        $response = $preset->process([
            "language" => Language::find($language)->value,
            "name" => $name,
            "age" => $age ?? 30,
            "job" => $job ?? "",
            "has_custom_instructions" => !empty($custom_instructions),
            "custom_instructions" => $custom_instructions,
        ]);

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

        // generate image
        $image = null;
        switch (Arr::get(Image::imageModelToProviderAndModel($model), "provider")) {
            case "stable_diffusion":
                $image = $this->getStableDiffusionImage($model, $prompt);
                break;
            case "ideogram":
                $image = $this->getIdeogramImage($model, $prompt);
                break;
            case "flux":
                $image = $this->getFluxImage($model, $prompt);
                break;
            case "dall_e":
                $image = $this->getDallEImage($model, $prompt);
                break;
            case "grok":
                $image = $this->getGrokImage($model, $prompt);
                break;
            case "gemini":
                $image = $this->getGeminiImage($model, $prompt);
                break;
        }

        if (!empty($image)) {
            return $image->image;
        }

        return null;
    }

    // image generators

    /**
     * Generate a stable diffusion image for the provided search
     *
     * @param string $model
     * @param string $prompt
     * @return StableDiffusion|null
     */
    private function getStableDiffusionImage(string $model, string $prompt)
    {
        // get model id
        $model = Arr::get(Image::imageModelToProviderAndModel($model), "model", "core");

        // make the payload
        if ($model === "sdxl") {
            // sdxl model
            $payload = [
                "text_prompts" => [
                    [
                        "text" => $prompt,
                        "weight" => 1
                    ]
                ]
            ];
        } else {
            // others models
            $payload = [
                "prompt" => $prompt,
                "model" => $model
            ];
        }

        // call the stability ai api
        $api = new StabilityAiClient();
        if ($model === "sdxl") {
            // sdxl model
            $artifact = $api->textToImage($payload);
            if (Arr::get($artifact, "artifacts.0.finishReason") === "SUCCESS") {
                return StableDiffusion::make(Arr::get($artifact, "artifacts.0.base64"));
            }
        } else {
            // others models
            $result = $api->stableImageGenerate($payload);

            if (!empty(Arr::get($result, "image", null))) {
                return StableDiffusion::make(Arr::get($result, "image"));
            }
        }

        return null;
    }

    /**
     * Generate a ideogram image for the provided search
     *
     * @param string $model
     * @param string $prompt
     * @return Ideogram|null
     */
    private function getIdeogramImage(string $model, string $prompt)
    {
        // get model id
        $modelData = Image::imageModelToProviderAndModel($model);
        $model = Arr::get($modelData, "model", "V_1_TURBO");

        // make the payload
        $payload = [
            "prompt" => $prompt,
            "rendering_speed" => Arr::get($modelData, "params.rendering_speed", "TURBO"),
        ];

        // call the ideogram api
        $api = new IdeogramClient();
        $result = $api->generate($payload);

        // make the Ideogram Images
        if (!empty(Arr::get($result, "data.0.url", null))) {
            return Ideogram::make(Arr::get($result, "data.0.url"));
        }

        return null;
    }

    /**
     * Generate a grok image for the provided search
     *
     * @param string $model
     * @param string $prompt
     * @return Ideogram|null
     */
    private function getGrokImage(string $model, string $prompt)
    {
        // get model id
        $model = Arr::get(Image::imageModelToProviderAndModel($model), "model", "grok-2-image");

        // make the payload
        $payload = [
            "prompt" => $prompt,
            "model" => $model
        ];

        // call the grok api
        $api = new XAiClient();
        $result = $api->image($payload);

        // make the Ideogram Images
        if (!empty(Arr::get($result, "data.0.b64_json", null))) {
            return Grok::make(Arr::get($result, "data.0.b64_json"));
        }

        return null;
    }

    /**
     * Generate a google image for the provided search
     *
     * @param string $model
     * @param string $prompt
     * @return Ideogram|null
     */
    private function getGeminiImage(string $model, string $prompt)
    {
        // get model id
        $model = Arr::get(Image::imageModelToProviderAndModel($model), "model", "gemini-2.5-flash-image");

        // make the payload
        $payload = [
            "model" => $model,
            "contents" => [
                ["parts" => [["text" => $prompt]]]
            ],
        ];

        // call the google api
        $api = new GeminiClient();
        $result = $api->image($payload);

        // make the Google Images
        $base64 = GeminiClient::getImageInlineData($result);
        if (!empty($base64)) {
            return Gemini::make($base64);
        }

        return null;
    }

    /**
     * Generate a Flux image for the provided search
     *
     * @param string $model
     * @param string $prompt
     * @return Flux|null
     */
    private function getFluxImage(string $model, string $prompt)
    {
        // get model id
        $model = Arr::get(Image::imageModelToProviderAndModel($model), "model", "flux-dev");

        // make the payload
        $payload = [
            "prompt" => $prompt,
            "model" => $model
        ];

        $api = new FluxClient();

        // create the tasks
        $response = $api->createTask($payload);

        // get the tasks ids
        $pollingUrl = Arr::get($response, "polling_url");

        // get the tasks results
        $result = [];
        while (true) {
            sleep(3);

            $result = $api->getResult($pollingUrl);

            $again = Arr::get($result, "status") === "Pending";

            if (!$again) {
                break;
            }
        }

        // make the Flux Images
        if (!empty(Arr::get($result, "result.sample", null))) {
            return Flux::make(Arr::get($result, "result.sample"));
        }

        return null;
    }

    /**
     * Generate a dall-e image for the provided search
     *
     * @param string $model
     * @param string $prompt
     * @return DallE|null
     */
    private function getDallEImage(string $model, string $prompt)
    {
        // get model id
        $modelData = Image::imageModelToProviderAndModel($model);
        $model = Arr::get($modelData, "model", "flux-dev");

        // make the settings
        $payload = [
            "model" => $model,
            "quality" => Arr::get($modelData, "params.quality", "auto"),
            "size" => "auto",
            "moderation" => "low",
        ];

        // call the openai api
        $api = new OpenAiClient();
        $result = $api->image($prompt, $payload);

        if (!empty(Arr::get($result, "data.0.b64_json"))) {
            return DallE::make(Arr::get($result, "data.0.b64_json"));
        }

        return null;
    }

    // validators
    private function validateLanguage()
    {
        $this->validate([
            "language" => ["required", "string"],
        ]);
    }

    private function validateUser()
    {
        $this->validate([
            "mode" => ["required", Rule::in(["create", "attach"])],
            "username" => [
                "required_if:mode,create",
                "string",
                function ($attribute, $value, $fail) {
                    if ($this->input("mode") === "create" && username_exists($value)) {
                        $fail('The ' . $attribute . ' field already exists.');
                    }
                }
            ],
            "password" => ["required_if:mode,create", "string"],
            "email" => ["nullable", "email", function ($attribute, $value, $fail) {
                if ($this->input("mode") === "create" && email_exists($value)) {
                    $fail('The ' . $attribute . ' field already exists.');
                }
            }],
            "role" => ["nullable", "string"],
            "user_id" => [
                "required_if:mode,attach",
                function ($attribute, $value, $fail) {

                    if ($this->input("mode") === "attach") {
                        $user = User::with("persona")->find($value);
                        if (!$user) {
                            $fail('The user does not exist.');
                            return;
                        }

                        if ($user->persona !== null) {
                            $fail('The user is already associated with a persona.');
                        }
                    }
                }
            ],
        ]);
    }

    private function validateProfil()
    {
        $this->validate([
            "first_name" => ["required", "string"],
            "last_name" => ["nullable", "string"],
            "age" => ["required", "numeric", "min:18", "max:99"],
            "job" => ["required", "string"],
            "writing_style" => ["nullable", "string"],
        ]);
    }

    private function validateBiography()
    {
        $this->validate([
            "description" => ["nullable", "string"],
        ]);
    }

    private function validateAvatar()
    {
        $this->validate([
            "avatar.attachment_id" => ["nullable"],
        ]);
    }

    public function validateSimpleLocalAvatar()
    {
        if (!class_exists("Simple_Local_Avatars")) {
            $this->response(["message" => "An error occurred", "error" => "This feature require the Simple Local Avatars plugin. It is available on the WordPress store."], 503);
        }
    }

    private function validateCustomInstructions()
    {
        $this->validate([
            "custom_instructions" => ["nullable", "string"],
        ]);
    }
    public function validateAPIKeys()
    {
        if (empty(Settings::get("api.stability_ai.api_key")) && empty(Settings::get("api.openai.api_key"))) {
            $this->response(["message" => "An error occurred", "error" => "Your OpenAI or Stability AI API Keys are not set."], 503);
        }
    }
}
