<?php namespace App\Libraries;

use Config\Services;
use Exception;

class GitHub
{
    private ?string $token;
    private ?string $username;
    private ?string $repo;
    private string $branchName;
    private string $baseApiUrl;
    private string $apiVersion;
    private string $templateOwner;
    private string $templateName;

    /**
     * Create models, config and library's
     */
    function __construct()
    {
        $settings = new Settings();
        $this->token = $settings->get_config("github_token");
        $this->username = $settings->get_config("github_username");
        $this->repo = $settings->get_config("github_repo");
        $this->branchName = $settings->get_config("github_branch");
        $this->baseApiUrl = "https://api.github.com/";
        $this->apiVersion = "2022-11-28";
        $this->templateOwner = env('templateRepo.owner') ?? 'sitenativedev';
        $this->templateName = env('templateRepo.name') ?? 'flangapp_pro_starter';
    }

    /**************************************************************************************
     * PUBLIC FUNCTIONS
     **************************************************************************************/

    /**
     * Create fork
     * @param string $token
     * @param string $user
     * @return array
     */
    public function create_fork(string $token, string $user): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$token
                ],
            ])->setJSON([
                "owner"   => $user,
                "name"    => "flangapp_pro",
                "private" => true
            ])->post("repos/".$this->templateOwner."/".$this->templateName."/generate");
            $data = json_decode($res->getBody());
            if (!empty($data->id)) {
                return ['event' => true];
            } else {
                return ['event' => false, 'message' => lang("Message.message_98")];
            }
        } catch (Exception $e) {
            return ['event' => false, 'message' => lang("Message.message_98")];
        }
    }

    /**
     * Create new branch for new app
     * @param string $name
     * @return array
     */
    public function create_branch(string $name): array
    {
        $target = $this->get_sha_repo();

        if (!$target["event"]) {
            return ['event' => false, 'message' => lang("Message.message_25")];
        }

        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->setJSON([
                "ref" => "refs/heads/".$name,
                "sha" => $target['sha']
            ])->post("repos/".$this->username."/".$this->repo."/git/refs");
            $data = json_decode($res->getBody());
            if (!empty($data->ref)) {
                return ['event' => true];
            } else {
                return ['event' => false, 'message' => lang("Message.message_26")];
            }
        } catch (Exception $e) {
            return ['event' => false, 'message' => lang("Message.message_26")];
        }
    }

    /**
     * Create new commit
     * @param string $branch
     * @param string $path
     * @param $content
     * @return array
     */
    public function create_commit(string $branch, string $path, $content): array
    {
        $hash = $this->get_sha_file($path, $branch);

        if (!$hash["event"]) {
            // File doesn't exist, use upload_commit
            return $this->upload_commit($branch, $path, $content);
        }

        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->setJSON([
                "message" => "update for file ".$path." - ".date('d-m-Y H:i'),
                "branch"  => $branch,
                "content" => base64_encode($content),
                "sha"     => $hash["sha"],
            ])->put("repos/".$this->username."/".$this->repo."/contents/".$path);
            $data = json_decode($res->getBody());
            if (!empty($data->content)) {
                return ['event' => true];
            } else {
                return ['event' => false, 'message' => lang("Message.message_27").": ".$path];
            }
        } catch (Exception $e) {
            print_r($e);
            return ['event' => false, 'message' => lang("Message.message_27").": ".$path];
        }
    }

    /**
     * Delete file
     * @param string $branch
     * @param string $path
     * @return array
     */
    public function delete_file(string $branch, string $path, bool $skipIfNotExists = false): array
    {
        $hash = $this->get_sha_file($path, $branch);

        if (!$hash["event"]) {
            // File doesn't exist, check the skip flag
            return $skipIfNotExists ? ['event' => true] : ['event' => false, 'message' => lang("Message.message_28")];
        }

        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->setJSON([
                "message" => "delete file ".$path,
                "branch"  => $branch,
                "sha"     => $hash["sha"]
            ])->delete("repos/".$this->username."/".$this->repo."/contents/".$path);
            
            $data = json_decode($res->getBody(), true);
            if (!empty($data['commit'])) {
                return ['event' => true];
            } else {
                return [
                    'event'   => false,
                    'message' => lang("Message.message_33")
                ];
            }
        } catch (Exception $e) {
            return [
                'event'   => false,
                'message' => lang("Message.message_33")
            ];
        }
    }

    /**
     * Upload new file
     * @param string $branch
     * @param string $path
     * @param $content
     * @return array
     */
    public function upload_commit(string $branch, string $path, $content): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->setJSON([
                "message" => "update for file ".$path." - ".date('d-m-Y H:i'),
                "branch"  => $branch,
                "content" => base64_encode($content),
                "path"    => $path
            ])->put("repos/".$this->username."/".$this->repo."/contents/".$path);
            
            $data = json_decode($res->getBody());
            if (!empty($data->content)) {
                return ['event' => true];
            } else {
                return ['event' => false, 'message' => lang("Message.message_27")];
            }
        } catch (Exception $e) {
            return ['event' => false, 'message' => lang("Message.message_27")];
        }
    }

    /**
     * Trigger GitHub Action workflow
     * @param string $workflowFileName
     * @param string $branchName
     * @param array $inputVariables
     * @return array
     */
    public function trigger_action(string $workflowFileName, string $branchName, array $inputVariables = []): array
    {
        try {
            $data = [
                "ref" => $branchName,
                "inputs" => $inputVariables
            ];

            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer {$this->token}",
                ],
            ])->setJSON($data)->post("/repos/{$this->username}/{$this->repo}/actions/workflows/{$workflowFileName}/dispatches");
            
            $responseCode = $res->getStatusCode();

            if ($responseCode === 204) {
                return ["event" => true];
            } else {
                return ["event" => false, "message" => lang("Message.message_99")];
            }
        } catch (Exception $e) {
            print_r($e);
            return ["event" => false, "message" => lang("Message.message_100")];
        }
    }

    /**
     * Returns the data of the latest GitHub Action Run
     * @param string $workflowFileName
     * @param string $branchName
     * @return array
     */
    public function get_last_action(string $workflowFileName, string $branchName): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer {$this->token}",
                ],
            ])->get("/repos/{$this->username}/{$this->repo}/actions/workflows/{$workflowFileName}/runs?branch={$branchName}");

            $runsData = json_decode($res->getBody(), true);

            if (empty($runsData['workflow_runs'])) {
                return ['event' => false, 'message' => lang("Message.message_101")];
            }

            $latestRun = $runsData['workflow_runs'][0];

            return [
                'event' => true,
                'status' => $latestRun['status'], // 'queued', 'in_progress', 'completed'
                'conclusion' => $latestRun['conclusion'] ?? 'in_progress', // 'success', 'failure', 'neutral', 'cancelled'
                'run_id' => $latestRun['id'],
            ];
        } catch (Exception $e) {
            return ['event' => false, 'message' => lang("Message.message_102")];
        }
    }

    /**
     * Get version.json file from repository
     * @param string $branch
     * @return array
     */
    public function get_repo_version(string $branch): array
    {
        $data = $this->get_file_content("version.json", $branch);

        if (!$data['event']) {
            return ['event' => false, 'message' => lang("Message.message_105")];
        }

        $versionJson = json_decode($data["content"], true);
        return ["event" => true, "data" => ["compatibility_date" => $versionJson["compatibility_date"]]];
    }

    /**
     * Get version.json file from template repository
     * @return array
     */
    public function get_template_version(): array
    {
        $data = $this->get_file_content_from_template("version.json");

        if (!$data['event']) {
            return ['event' => false, 'message' => lang("Message.message_105")];
        }

        $versionJson = json_decode($data["content"], true);
        return ["event" => true, "data" => ["compatibility_date" => $versionJson["compatibility_date"]]];
    }

    /**
     * Create a merge commit to merge a branch into a target branch
     * @param string $head The name of the branch you want to merge FROM
     * @param string $base The name of the branch you want to merge INTO (target branch)
     * @return array
     */
    public function create_merge_commit(string $head, string $base): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->setJSON([
                "base"          => $base,
                "head"          => $head,
                "commit_message" => "Merge branch '{$head}' into '{$base}' - " . date('d-m-Y H:i')
            ])->post("repos/".$this->username."/".$this->repo."/merges");
            
            $statusCode = $res->getStatusCode();
            
            // Status 201 means merge was successful
            // Status 204 means branch is already up to date (nothing to merge)
            if ($statusCode === 201 || $statusCode === 204) {
                return ['event' => true];
            } else {
                return ['event' => false, 'message' => lang("Message.message_112")];
            }
        } catch (Exception $e) {
            return ['event' => false, 'message' => lang("Message.message_112")];
        }
    }

    /**************************************************************************************
     * PRIVATE FUNCTIONS
     **************************************************************************************/

    /**
     * Get Sha for file
     * @param string $path
     * @param string $branch
     * @return array|false[]
     */
    private function get_sha_file(string $path, string $branch): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->get("repos/".$this->username."/".$this->repo."/contents/".$path."?ref=".$branch);
            $data = json_decode($res->getBody());
            if (!empty($data->sha)) {
                return ["event" => true, "sha" => $data->sha];
            } else {
                return ["event" => false];
            }
        } catch (Exception $e) {
            return ["event" => false];
        }
    }

    /**
     * Get SHA of file from template repository
     * @param string $path Path to the file
     * @return array
     */
    private function get_sha_file_from_template(string $path): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->get("repos/".$this->templateOwner."/".$this->templateName."/contents/".$path."?ref=main");
            $data = json_decode($res->getBody());
            if (!empty($data->sha)) {
                return ["event" => true, "sha" => $data->sha];
            } else {
                return ["event" => false];
            }
        } catch (Exception $e) {
            return ["event" => false];
        }
    }

    /**
     * Get Sha hash for repo
     * @return array|false[]
     */
    private function get_sha_repo(): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->get("repos/".$this->username."/".$this->repo."/branches/".$this->branchName);
            $data = json_decode($res->getBody());
            if (!empty($data->commit)) {
                return ["event" => true, "sha" => $data->commit->sha];
            } else {
                return ["event" => false];
            }
        } catch (Exception $e) {
            return ["event" => false];
        }
    }

    /**
     * Get file content from current repository
     * @param string $path Path to the file
     * @return array
     */
    private function get_file_content(string $path, string $branch): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->get("repos/".$this->username."/".$this->repo."/contents/".$path."?ref=".$branch);
            
            $data = json_decode($res->getBody());
            if (!empty($data->content)) {
                return [
                    "event" => true, 
                    "content" => base64_decode($data->content),
                    "sha" => $data->sha
                ];
            }
            return ["event" => false];
        } catch (Exception $e) {
            return ["event" => false];
        }
    }

    /**
     * Get file content from template repository
     * @param string $path Path to the file
     * @return array
     */
    private function get_file_content_from_template(string $path): array
    {
        try {
            $res = Services::curlrequest([
                "baseURI"     => $this->baseApiUrl,
                "headers"     => [
                    "Content-Type"         => "application/json",
                    "X-GitHub-Api-Version" => $this->apiVersion,
                    "User-Agent"           => "Flangapp PRO API Server",
                    "Authorization"        => "Bearer ".$this->token,
                ],
            ])->get("repos/".$this->templateOwner."/".$this->templateName."/contents/".$path."?ref=main");
            $data = json_decode($res->getBody());

            if (!empty($data->content)) {
                return [
                    "event" => true, 
                    "content" => base64_decode($data->content),
                    "sha" => $data->sha
                ];
            }
            return ["event" => false];
        } catch (Exception $e) {
            echo('Exception');
            return ["event" => false];
        }
    }
}