<?php

class Rbac_generator_model extends Cf_Model
{
    const API_APPLICATION_NAME = 'API';

    const MP_APPLICATION_NAME = 'Mission Portal';
    const API_PATH = FCPATH . 'api';
    const COMMENT_PATTERN = '/\/\*(.*?)\*\//s';

    const ALIAS_PATTERN = '/@rbacAlias(.*?)\*/s';
    const RBAC_NAME_PATTERN = '/@rbacName(.*?)\*/s';
    const RBAC_GROUP_PATTERN = '/@rbacGroup(.*?)\*/s';
    const RBAC_DESCRIPTION_PATTERN = '/@rbacDescription(.*?)\*/s';
    const RBAC_DEFAULT_PATTERN = '@rbacAllowedByDefault';
    const RED = "\033[0;31m";

    const GREEN = "\033[0;32m";
    const NC = "\033[0m";
    private $phpFiles = [];

    private $permissionList = false;
    private $rbacData = [];
    private $insert = [];
    private $update = [];
    private $delete = [];


    public function setPermissionList($list)
    {
        $this->permissionList = [];
        foreach ($list as $applications) {
            foreach ($applications as $groups) {
                foreach ($groups as $permission) {
                    $this->permissionList[] = $permission;
                }
            }
        }
    }

    public function generateSQL($filesRootPath)
    {
        $return = '';
        $this->collectPhpFiles($filesRootPath);
        $this->findRbacAnnotations();
        $this->prepareData();
        $return .= $this->generateInsertStatements();
        $return .= $this->generateUpdateStatements();
        $return .= $this->generateDeleteStatements();
        return $return;
    }

    private function collectPhpFiles($path)
    {
        $files = array_diff(scandir($path), ['.', '..']);

        foreach ($files as $key => $filePath) {
            $realpath = realpath($path . DIRECTORY_SEPARATOR . $filePath);
            if (is_file($realpath)) {
                if (pathinfo($realpath, PATHINFO_EXTENSION) == 'php') {
                    $this->phpFiles[] = $realpath;
                }
            } elseif (is_dir($realpath)) {
                $this->collectPhpFiles($realpath);
            }
        }
    }

    private function findRbacAnnotations()
    {
        foreach ($this->phpFiles as $file) {
            $content = file_get_contents($file);
            $commentMatches = [];
            preg_match_all(self::COMMENT_PATTERN, $content, $commentMatches);
            foreach ($commentMatches[0] as $match) {
                $alias = $this->extractInfo(self::ALIAS_PATTERN, $match);
                if ($alias == '') {
                    continue;
                }

                $this->rbacData[$alias] = [
                    'alias' => $alias,
                    'name' => $this->extractInfo(self::RBAC_NAME_PATTERN, $match),
                    'group' => $this->extractInfo(self::RBAC_GROUP_PATTERN, $match),
                    'allowed_by_default' => boolval(strstr($match, self::RBAC_DEFAULT_PATTERN)),
                    'description' => $this->extractInfo(self::RBAC_DESCRIPTION_PATTERN, $match),
                    'application' => strstr($file, self::API_PATH) ? 'API' : 'Mission Portal'
                ];

            }
        }
    }

    private function extractInfo($pattern, $text)
    {
        $matches = [];
        preg_match($pattern, $text, $matches);
        return isset($matches[1]) ? trim($matches[1]) : '';
    }

    private function prepareData()
    {
        $this->delete = array_diff(array_column($this->permissionList, 'alias'), array_keys($this->rbacData));
        foreach ($this->rbacData as $alias => $data) {
            $key = array_search($alias, array_column($this->permissionList, 'alias'));
            if ($key === false) {
                $this->insert[] = $data;
                continue;
            }

            $diff = array_diff_assoc($data, $this->permissionList[$key]);
            if (sizeof($diff) > 0) {
                //transform boolean true|false values to string
                if (isset($diff['allowed_by_default'])) {
                    $diff['allowed_by_default'] = var_export($diff['allowed_by_default'], true);
                }
                $this->update[$alias] = $diff;
            }
        }
    }

    private function generateInsertStatements()
    {
        $return = '';
        if (sizeof($this->insert) == 0) {
            $return .=  self::RED;
            $return .=  PHP_EOL . 'There are no new rbac permissions.' . PHP_EOL;
            $return .=  self::NC;
            return $return;
        }

        $return .=  self::GREEN;
        $query = str_repeat(PHP_EOL, 2) .
            'INSERT INTO rbac_permissions ("' . implode('", "', array_keys($this->insert[0])) . '") ' . PHP_EOL .
            'VALUES' . PHP_EOL;

        foreach ($this->insert as $key => $insert) {
            //transform boolean true|false values to string
            if (isset($insert['allowed_by_default'])) {
                $insert['allowed_by_default'] = var_export($insert['allowed_by_default'], true);
            }
            foreach($insert as $index => $value) {
                $insert[$index] = str_replace("'", "''", $value);
            }
            $query .= "('" . implode("', '", array_values($insert)) . "')";
            $query .= isset($this->insert[($key + 1)]) ? ',' : PHP_EOL . 'ON CONFLICT (alias)  DO NOTHING;';
            $query .= PHP_EOL;
        }

        $query .= str_repeat(PHP_EOL, 2);

        $query .= 'INSERT INTO rbac_role_permission ("role_id", "permission_alias") ' . PHP_EOL .
                  'VALUES' . PHP_EOL;

        foreach ($this->insert as $key => $insert) {
            $query .= "('admin', '" . $insert['alias'] . "')";
            $query .= isset($this->insert[($key + 1)]) ? ',' : PHP_EOL . 'ON CONFLICT (role_id, permission_alias)  DO NOTHING;';
            $query .= PHP_EOL;
        }
        $query .= str_repeat(PHP_EOL, 2);

        $return .=  $query;
        $return .=  self::NC;
        return $return;
    }

    private function generateUpdateStatements()
    {
        $return = '';
        if (sizeof($this->update) == 0) {
            $return .=  self::RED;
            $return .=  PHP_EOL . 'There are no rbac permissions to update.' . PHP_EOL;
            $return .=  self::NC;
            return $return;
        }

        $return .=  str_repeat(PHP_EOL, 2);
        $return .=  self::GREEN;

        foreach ($this->update as $alias => $update) {
            $query = 'UPDATE rbac_permissions SET ';
            $i = 0;
            $count = sizeof($update);
            foreach ($update as $column => $value) {
                $i++;
                $query .= '"' . $column . '" = \'' . str_replace("'", "''", $value) . '\'';
                $query .= $i == $count ? ' ' : ', ';

            }
            $escapedAlias = str_replace("'", "''", $alias);
            $query .= 'WHERE "alias" = \'' . $escapedAlias . '\';';
            $query .= PHP_EOL;
            $return .=  $query;
        }
        $return .=  self::NC;
        return $return;

    }

    private function generateDeleteStatements()
    {
        $return = '';
        if (sizeof($this->delete) == 0) {
            $return .=  self::RED;
            $return .=  PHP_EOL . 'There are no rbac permissions to delete.' . PHP_EOL;
            $return .=  self::NC;
            return $return;
        }

        $return .=  str_repeat(PHP_EOL, 2);
        $return .=  self::GREEN;

        foreach ($this->delete as $alias) {
            $escapedAlias = str_replace("'", "''", $alias);
            $return .=  'DELETE FROM rbac_permissions WHERE "alias" = \'' . $escapedAlias . '\';';
            $return .=  PHP_EOL;
        }
        $return .=  self::NC;
        $return .=  str_repeat(PHP_EOL, 2);
        return $return;
    }
}