<?php

namespace App\Browser\Jobs\InstagramRepost;

use App\Browser\JobDebugScreenshot;
use App\Browser\Jobs\Instagram\InstagramAbstractJob;
use App\Browser\Jobs\InstagramRepost\DescriptionPipeline\InstagramDescriptionPipeline;
use App\Models\InstagramRepost;
use App\Models\InstagramAccount;
use App\Models\Job;
use App\Models\JobArtifact;
use App\Models\JobRun;
use App\Notification\Notifications\JobDebugNotification;
use App\Notification\Providers\AllNotification;
use Facebook\WebDriver\WebDriverBy;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Laravel\Dusk\Browser;
use App\Services\AIPrompt\OpenAPIPrompt;

class InstagramRepostJob extends InstagramAbstractJob implements ShouldBeUniqueUntilProcessing
{
    // === CONFIGURATION ===

    public $timeout = 1800; // 30 minutes

    private const APPROXIMATIVE_RUNNING_MINUTES = 2;

    protected IInstagramVideoDownloader $videoDownloader;

    protected ReelDescriptor $ReelDescriptor;

    protected string $downloadFolder = "app/Browser/downloads/InstagramRepost/";

    /**
     * Pipeline for processing Instagram post descriptions.
     * This pipeline can be used to modify the description before reposting.
     * For example, it can remove account references or add hashtags.
     *
     * @var InstagramDescriptionPipeline
     */
    protected InstagramDescriptionPipeline $descriptionPipeline;

    public function __construct($jobId = 4, ReelDescriptor $ReelDescriptor = null, OpenAPIPrompt $openAPIPrompt = null)
    {
        parent::__construct($jobId, $openAPIPrompt);

        $this->downloadFolder = base_path($this->downloadFolder);
        $this->videoDownloader = new YTDLPDownloader();
        $this->ReelDescriptor = $ReelDescriptor ?? app(ReelDescriptor::class);
        $this->descriptionPipeline = new InstagramDescriptionPipeline([
            // Add steps to the pipeline here
            new DescriptionPipeline\RemoveAccountsReferenceStep(),
        ]);
    }

    public function run(Browser $browser): ?JobRun
    {
        Log::info("Running InstagramRepostJob");
        $this->jobInfos = Job::find($this->jobId)->jobInfosTable();
        $this->jobRun = new JobRun([
            "job_id" => $this->jobId,
            "success" => false,
        ]);
        $this->jobRun->save();

        $browser->visit('https://instagram.com');
        sleep(5);
        $this->removePopups($browser);
        sleep(2);
        $this->signin($browser);
        sleep(2);
        $this->repostLatestPosts($browser);
        sleep(5);

        $this->jobRun->success = true;
        $this->jobRun->save();

        Log::info("InstagramRepostJob run ended");

        return $this->jobRun;
    }

    /**
     * @inheritDoc
     */
    public function runTest(Browser $browser): ?JobRun
    {
        $this->jobInfos = Job::find($this->jobId)->jobInfosTable();
        try {
            $browser->visit('https://instagram.com');
            sleep(2);
            $this->removePopups($browser);
            sleep(2);
            $this->signin($browser);
            sleep(3);
            return $this->makeSimpleJobRun(
                true,
                "Connexion réussie",
                "Datboi a réussi à se connecter sur Instagram"
            );
        } catch (\Exception $e) {
            return $this->makeSimpleJobRun(
                false,
                "Connexion échouée",
                "Datboi n'a pas réussi à se connecter sur Instagram :\n" . $e->getMessage()
            );
        }
    }

    protected function repostLatestPosts(Browser $browser) {

        try {
            // Download the latest reels from the accounts specified in the job infos
            $toDownloadReels = []; // Array to store to download reels to post later

            $accounts = explode(",", $this->jobInfos->get("instagram_repost_accounts"));
            foreach ($accounts as &$account) {
                $account = trim($account);

                $toDownloadReels = array_merge($toDownloadReels, $this->getLatestReelsFromAccount($browser, $account));
            }

            // Add unreposted reels to the job run if not enough reels were downloaded
            if (count($toDownloadReels) < config("jobs.instagramRepost.max_reposts_per_job")) {
                $toDownloadReelsIds = array_map(function ($reel) {
                    return $reel->reel_id;
                }, $toDownloadReels);

                $unrepostedReels = InstagramRepost::where("reposted", false)
                    ->where("repost_tries", "<", config('jobs.instagramRepost.max_repost_tries')) // Limit to 3 tries
                    ->whereNotIn("reel_id", $toDownloadReelsIds) // Avoid already downloaded reels
                    ->whereIn("account_id", InstagramAccount::whereIn("username", $accounts)->pluck("id"))
                    ->take(config("jobs.instagramRepost.max_reposts_per_job") - count($toDownloadReels))
                    ->get();

                foreach ($unrepostedReels as $reel) {
                    $toDownloadReels[] = $reel;
                }
            }

            // Shuffling and keeping only the x first posts
            shuffle($toDownloadReels);
            $toDownloadReels = array_slice($toDownloadReels, 0, config("jobs.instagramRepost.max_reposts_per_job"));

            $this->jobRun->addArtifact(new JobArtifact([
                "name" => count($toDownloadReels) . " reels sélectionnés pour être repost",
                "content" => ""
            ]));

            // Download the reels
            /**
             * @var mixed[]
             */
            $downloadedReels = [];
            foreach ($toDownloadReels as $repost) {
                $downloadInfos = $this->downloadReel(
                    $browser,
                    $repost
                );

                $downloadedReels[] = [
                    $repost,
                    $downloadInfos
                ];

                if ($downloadInfos !== null) {
                    $this->describeReel($repost, $downloadInfos);
                }

            }

            $this->jobRun->addArtifact(new JobArtifact([
                "name" => count($downloadedReels) . " reels sélectionnés qui ont été téléchargés",
                "content" => ""
            ]));

            // Now repost all downloaded reels
            $repostedReelsCounter = 0;
            foreach ($downloadedReels as $infos) {
                $reel = $infos[0];
                $videoInfo = $infos[1];

                if ($videoInfo === null) {
                    continue; // Skip this reel if it failed to download
                }

                $repostSuccess = false;
                do {
                    $repostSuccess =  $this->repostReel($browser, $reel, $videoInfo);
                } while (!$repostSuccess && $reel->repost_tries < config("jobs.instagramRepost.max_repost_tries"));
                $repostedReelsCounter += $repostSuccess;
            }

            $this->jobRun->addArtifact(new JobArtifact([
                "name" => $repostedReelsCounter . " reels sélectionnés qui ont été repostés",
                "content" => ""
            ]));
        } catch (\Exception $e) {
            $browser->screenshot(JobDebugScreenshot::getFileName($this->jobId));
            AllNotification::send(new JobDebugNotification($this->jobId, "Unknown error when trying to repost reels : " . $e->getMessage()));
        } finally {
            // Removes all videos in the download folder
            $files = glob($this->downloadFolder . '*'); // Get all files in the download folder
            foreach ($files as $file) {
                if (is_file($file)) {
                    unlink($file); // Delete the file
                }
            }
        }
    }

    private function getLatestReelsFromAccount(Browser $browser, string $account, int $maxReels = null, bool $save = true): array
    {
        if ($maxReels === null) {
            $maxReels = config("jobs.instagramRepost.max_reposts_per_account");
        }

        $accountReels = []; // Indexed array to store new reels from the account

        $browser->visit("https://instagram.com/{$account}/reels");
        sleep(3);
        $browser->waitForText("followers", 10, true);

        // If we are here, the account exists
        $accountModel = InstagramAccount::where("username", $account)->first();
        if ($accountModel == null) { // Does not exist in the database yet
            $accountModel = new InstagramAccount([
                "username" => $account,
            ]);
            $accountModel->save();
            Log::info("New Instagram account added: {$account}");
        } else {
            Log::debug("Instagram account already exists: {$account}");
        }

        $repostedPosts = $accountModel->reposts()->where("reposted", true)->pluck("reel_id");

        // Posts must be sorted by latest post date first
        // TODO : Scroll when not enough post are shown
        $posts = $browser->driver->findElements(WebDriverBy::xpath('//a[contains(@href, "'.$account.'/reel/")][not(.//*[local-name() = "svg"][@aria-label="Pinned post icon"])]'));

        foreach ($posts as $post) {
            $postUrl = $post->getAttribute('href');
            $postId = explode("/", $postUrl)[3] ?? null;

            if ($postId === null) {
                AllNotification::send(new JobDebugNotification($this->jobId, "Can't get Instagram post ID from url"));
                continue;
            }

            // Break the loop if the post has already been reposted
            if ($repostedPosts->contains($postId)) {
                Log::debug("Post already reposted: {$postUrl}");
                break;
            }

            // Get the model for the reel
            $query = InstagramRepost::where("reel_id", $postId)
                ->where("account_id", $accountModel->id)
                ->first();

            if ($query) {
                $reelModel = $query;
            } else {
                $reelModel = new InstagramRepost([
                    "reel_id" => $postId,
                    "account_id" => $accountModel->id,
                    "reposted" => false,
                    "repost_tries" => 0
                ]);
                if ($save) {
                    $reelModel->save();
                }
            }

            if (count($accountReels) < $maxReels) {
                $accountReels[] = $reelModel; // Add it to the to be downloaded reels array
            }
        }

        return $accountReels;
    }

    private function downloadReel(Browser $browser, InstagramRepost $reel): ?IInstagramVideo
    {
        $videoInfo = $this->videoDownloader->downloadVideo(
            $this->jobId,
            $this->jobRun,
            $reel->getUrl(),
            $this->downloadFolder,
            $this->jobInfos->get("instagram_repost_account_username"),
            $this->jobInfos->get("instagram_repost_account_password")
        );

        if ($videoInfo === null) {
            Log::error("Failed to download video for post: {$reel->reel_id}");
            return null;
        }
        else {
            // Set the filename to the post ID
            $newFilename = $this->downloadFolder . $reel->reel_id . ".mp4";
            rename($videoInfo->getFilename(), $newFilename);
            $videoInfo->setFilename($newFilename);
        }

        Log::info("Downloaded video: {$videoInfo->getTitle()} : {$videoInfo->getDescription()}");

        return $videoInfo;
    }

    protected function describeReel(InstagramRepost $reel, IInstagramVideo $videoInfo): void
    {
        // Set the video description to the reel description
        $reel->video_description = $this->ReelDescriptor->getDescription($videoInfo->getFilename());
        $reel->save();

        Log::info("Reel description set: {$reel->reel_id} - {$reel->video_description}");
    }

    protected function repostReel(Browser $browser, InstagramRepost $reel, IInstagramVideo $videoInfo): bool
    {
        try {
            Log::info("Reposting reel: {$reel->reel_id} - {$videoInfo->getTitle()}");

            // Increment the repost tries
            $reel->repost_tries++;
            $reel->save();

            // TODO Reset if a problem occurs and try again with a limit of 3 attempts
            $browser->visit('https://instagram.com');
            sleep(2);

            // Navigate to the reel upload page
            $createButton = $browser->driver->findElement(WebDriverBy::xpath('//a[./div//span[contains(text(), "Create")]]'));
            $createButton->click();
            sleep(2);
            $newPostButton = $browser->driver->findElement(WebDriverBy::xpath('//a[./div//span[contains(text(), "Post")]][@href="#"]'));
            $newPostButton->click();
            sleep(3);

            // Upload the video file
            $selectFileButton = $browser->driver->findElement(WebDriverBy::xpath('//button[contains(text(), "Select from computer")]'));
            $selectFileButton->click();
            sleep(2);
            $browser->attach('input[type="file"]', $this->downloadFolder . $reel->reel_id . ".mp4");

            sleep(10); // TODO : Wait for the file to be uploaded

            $this->removePopups($browser);
            sleep(2);

            // Put original resolution
            $this->putOriginalResolution($browser);

            $this->clickNext($browser);
            $this->clickNext($browser); // Skip cover photo and trim

            // Add a caption
            $captionText = $this->descriptionPipeline->process($this->getReelCaption($reel, $videoInfo));
            $this->pasteText($browser, $captionText, 'div[contenteditable]');

            sleep(2); // Wait for the caption to be added

            if (config("app.env") !== "local") { // Don't share the post in local environment
                $this->clickNext($browser); // Share the post
            }

            sleep(7); // Wait for the post to be completed
            $this->removePopups($browser);

            // Check if the post was successful
            try {
                $browser->waitForText("Your reel has been shared.", 60, true);
                Log::info("Reel reposted successfully: {$reel->reel_id}");

                // Mark the reel as reposted in the database
                $reel->reposted = true;
                $reel->save();

                // set the repost ID to the lastest account reel ID
                $reel->repost_reel_id = $this->getLatestPostId($browser, $reel);
                $reel->save();

                return true;

            } catch (\Exception $e) {
                try {
                    $browser->waitForText("Your post was shared", 60, true);
                    $closeButton = $browser->driver->findElement(WebDriverBy::xpath('//div[./div/*[local-name() = "svg"][@aria-label="Close"]]'));
                    $closeButton->click();
                } catch (\Exception $e) {
                    Log::error("Failed to repost reel: {$reel->reel_id} - " . $e->getMessage());
                    $browser->screenshot(JobDebugScreenshot::getFileName($this->jobId));
                    AllNotification::send(new JobDebugNotification($this->jobId, "Failed to repost reel: {$reel->reel_id} - " . $e->getMessage()));
                    return false;
                }
                return true; // Reel reposted successfully, no error message found
            }
        } catch (\Exception $e) {
            Log::error("Failed to repost reel: {$reel->reel_id} - " . $e->getMessage());
            $browser->screenshot(JobDebugScreenshot::getFileName($this->jobId));
            AllNotification::send(new JobDebugNotification($this->jobId, "Failed to repost reel: {$reel->reel_id} - " . $e->getMessage()));
            return false;
        }
    }

    private function getLatestPostId(Browser $browser, InstagramRepost $reel): ?string
    {
        // Go to the profile page of the managed account
        $newReel = $this->getLatestReelsFromAccount(
            $browser,
            $this->jobInfos->get('instagram_repost_account_username'),
            1,
            false // Don't save the reel, we don't want to repost our own reposts
        )[0] ?? null;

        if ($newReel === null) {
            Log::error("No reels found for account: " . $this->jobInfos->get('instagram_repost_account_username'));
            return null;
        }

        // Return the reel ID
        return $newReel->reel_id;
    }

    private function getReelCaption(InstagramRepost $reel, IInstagramVideo $videoInfo): string
    {
        if (isset($reel->instagram_caption)) {
            return $reel->instagram_caption;
        }

        // Get the reel description from the database or the video info
        $reelDescription = $reel->video_description;
        $originalDescription = $videoInfo->getDescription();
        $llmAnswer = $this->openAPIPrompt->generate(
            config('llm.models.chat.name'),
            "Original Caption: {$originalDescription}
llm_description: {$reelDescription}",
            [],
            outputFormat: '{"type": "object", "properties": {"answer": {"type": "string"}}, "required": ["answer"]}',
            systemMessage: "You are an expert Instagram caption writer. Your primary goal is to create short, engaging, concise captions for social media reels that capture the fun or relatability of the content without simply describing it like a transcript summary.

Captions must:
1.  Be brief and punchy.
2.  Capture the essence or mood of the video.
3.  Relate directly to the provided description (if available) or the core concept if no specific LLM description is given, but avoid copying phrasing awkwardly.
4.  Encourage engagement relevant to the platform's algorithm (e.g., asking a question related to the joke/scene).
5.  Optionally include relevant hashtags at the end (#hashtagsOnly), chosen appropriately for the reel's content or vibe. Use common tags if no specific ones are provided, but avoid overly generic ones unless fitting.
6.  If credit information is provided in the input (e.g., `credit: twitteruser`), acknowledge it minimally within the caption text *using only that source*. Do not invent any account handles (`@`) or platform prefixes (`tt/`). Use phrases like \"Credited to...\" or simply insert the credited name if appropriate, but don't force it unless the core concept naturally includes attribution. If no credit is provided, do not mention a specific creator.

**Do Not:**
*   Start captions with 'This reel...' or similar intros.
*   Describe the video content directly (replacing the LLM description role).
*   Include platform-specific mentions (`tt/`, `@`) unless naturally part of the credited source's name format itself and used minimally as instructed for credit handling.
*   Use overly complex sentences, slang that doesn't fit (#hashtags can be used), or long-winded explanations. Keep it to 1-3 short lines maximum.

**Emojis:**
*   Feel free to use emojis in moderation (e.g., 😂, 🤣, 😜, 👀) to add visual flair and emotion.
*   They should enhance the caption but not be the *main* focus. Avoid excessive or random emojis that look unprofessional.

Your response format must strictly adhere to JSON with only one required field: `answer`. Provide ONLY the generated caption string in this `answer` field, no explanations, markdown formatting, or other text.",
            keepAlive: true,
            shouldThink: config('llm.models.chat.shouldThink')
        );
        $llmAnswer = json_decode($llmAnswer, true)['answer'] ?? null;
        if ($llmAnswer !== null) {
            $reel->instagram_caption = $llmAnswer;
            $reel->save();
            Log::info("Reel caption generated: {$reel->reel_id} - {$llmAnswer}");
        }
        return $llmAnswer;
    }
}
