<?php


namespace app\controllers;

use app\components\Utils;
use app\models\EmailTemplate;
use app\models\LoginForm;
use app\models\TermosUso;
use app\models\TermosUsoAceiteUsuario;
use app\models\User;
use app\models\UserApi;
use app\models\UserHasResource;
use app\models\UserRefreshToken;
use app\modules\performance\models\ClienteTemModulo;
use app\modules\performance\models\Contexto;
use app\modules\performance\models\ContextoTemModulo;
use DateTimeImmutable;
use Exception;
use Lcobucci\JWT\Parser;
// sizeg\jwt\Jwt removido na migracao para lcobucci/jwt 4.3
use stdClass;
use Yii;
use yii\base\Security;
use yii\data\ActiveDataProvider;
use yii\helpers\Url;
use yii\rest\ActiveController;
use yii\validators\EmailValidator;
use yii\web\Response;


class AuthController extends ActiveController
{

    public $modelClass = "app\models\User";

    public $serializer = [
        'class' => 'yii\rest\Serializer',
    ];

    public function init()
    {
        parent::init();
        \Yii::$app->user->enableSession = false;
    }

    public function behaviors()
    {
        $behaviors = parent::behaviors();

        $behaviors['authenticator'] = [
            'class' => \app\components\JwtHttpBearerAuth::class,
            'except' => [
                'login',
                'forgot-password',
                'refresh-token',
            ],
        ];

        return $behaviors;
    }

    protected function verbs()
    {
        $verbs = parent::verbs();
        $verbs = [
            'login' => ['POST'],
            'refresh-token' => ['POST'],
            'self' => ['GET'],
        ];
        return $verbs;
    }


    public function actionIndex()
    {
        return "Index";
    }

    public function actionLogin()
    {

        //Buscando valores da requisição
        $post = Yii::$app->request->post();

        //Verifica se preencheu usuário
        if (!isset($post['username'])) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '1 - Usuário ou senha inválidos'
            ];
        }

        //Verifica se preencheu senha
        if (!isset($post['password'])) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '2 - Usuário ou senha inválidos'
            ];
        }

        $usuario = $post['username'];
        $password = $post['password'];
        $acessos = [];

        $user = \app\models\User::find()
            ->where([
                'username' => $usuario,
                'status' => User::STATUS_ACTIVE,
            ])
            ->one();

        if (!$user) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '3 - Usuário ou senha inválidos',
            ];
        }


        if (\Yii::$app->security->validatePassword($password, $user->password_hash)) {

            $user->logado = User::LOGADO_SIM;

            if (!$user->save()) {

                Yii::$app->response->statusCode = 401;

                return [
                    'mensagem' => '3.1 - Erro ao logar o usuário',
                ];
            }

            \Yii::$app->user->setIdentity($user);
            $roles = \Yii::$app->authManager->getRolesByUser(\Yii::$app->user->id);
            foreach ($roles as $k => $v) {
                $acessos[] = $k;
            }
        } else {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '4 - Usuário ou senha inválidos',
            ];
        }

        //Buscando apenas as informações importantes do usuário
        $userObject = json_decode(json_encode($user['attributes']), false);

        try {
            $token = $this->generateJwt($userObject);
        } catch (Exception $e) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '5 -Erro ao gerar o JWT: ' . $e->getMessage()
            ];
        }

        try {
            $refreshToken = $this->generateRefreshToken($userObject);
        } catch (Exception $e) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '6 - Erro ao gerar o refresh token JWT: ' . $e->getMessage()
            ];
        }

        $recursos = [];

        $temAdmin = false;
        foreach ($acessos as $acesso) {
            if ($acesso == 'Admin' || $acesso == 'Administrador') {

                if (!$temAdmin) {
                    $recursos[] = [
                        'tipo' => 'ADM',
                        'recurso' => '1',
                    ];

                    $temAdmin = true;
                }
            }
        }



        $userHasResources = UserHasResource::find()
            ->where(['user_id' => $user->id])
            ->orderBy('role ASC, resource_id ASC')
            ->all();

        foreach ($userHasResources as $userHasResource) {

            if ($userHasResource->role == 'C-Operational - Nível 2 - Operador') {
                $recursos[] = [
                    'tipo' => 'OP',
                    'recurso' => $userHasResource->resource_id,
                ];
            }

            if ($userHasResource->role == 'C-Performance - Nível 1 - Cliente Gestor') {
                $recursos[] = [
                    'tipo' => 'AN',
                    'recurso' => $userHasResource->resource_id,
                ];
            }
        }

        $enterprisesInfo = [];


        $enterprisesInfo[] = [
                    'id' => 7,
                    'name' => "CSIE | Eletrobras",
                ];
                
        /*
        if ($temAdmin) {

            $contextoTemModulos = ContextoTemModulo::find()
                ->select('contexto_id')
                ->where(['modulo' => ClienteTemModulo::MODULO_OPERATIONAL])
                ->column();

            $contextos = Contexto::find()
                ->joinWith('cliente')
                ->where([
                    'contexto.active' => Contexto::ATIVO,
                ])
                ->andWhere(['in', 'contexto.id', $contextoTemModulos])
                ->groupBy('cliente.id')
                ->orderBy('cliente.nome ASC')
                ->all();

            foreach ($contextos as $contexto) {
                $enterprisesInfo[] = [
                    'id' => $contexto->cliente_id,
                    'name' => $contexto->cliente->nome,
                ];
            }
        } else {

            $contextosA = [];

            foreach ($userHasResources as $userHasResource) {

                if ($userHasResource->role == 'C-Operational - Nível 2 - Operador') {
                    $contextosA[] = $userHasResource->resource_id;
                }

                if ($userHasResource->role == 'C-Performance - Nível 4 - Contexto Gestor') {
                    $contextosA[] = $userHasResource->resource_id;
                }

                if ($userHasResource->role == 'C-Performance - Nível 1 - Cliente Gestor') {

                    $contextos = Contexto::find()
                        ->where([
                            'cliente_id' => $userHasResource->resource_id,
                            'active' => Contexto::ATIVO,
                        ])
                        ->all();

                    foreach ($contextos as $c) {
                        $contextosA[] = $c->id;
                    }
                }



                $contextoTemModulos = ContextoTemModulo::find()
                    ->select('contexto_id')
                    ->where(['modulo' => ClienteTemModulo::MODULO_OPERATIONAL])
                    ->andWhere(['in', 'contexto_id', $contextosA])
                    ->groupBy('contexto_id')
                    ->column();

                $contextos = Contexto::find()
                    ->joinWith('cliente')
                    ->where([
                        'contexto.active' => Contexto::ATIVO,
                    ])
                    ->andWhere(['in', 'contexto.id', $contextoTemModulos])
                    ->groupBy('cliente.id')
                    ->orderBy('cliente.nome ASC')
                    ->all();
            }

            foreach ($contextos as $contexto) {
                $enterprisesInfo[] = [
                    'id' => $contexto->cliente_id,
                    'name' => $contexto->cliente->nome,
                ];
            }
        }
            */

        $acceptTermsInfo = [];

        $termoUso = TermosUso::find()
            ->where([
                'tipo' => TermosUso::TERMOS_USO,
                'status' => TermosUso::ATIVO,
            ])
            ->orderBy('id DESC')
            ->one();

        $politicaPrivacidade = TermosUso::find()
            ->where([
                'tipo' => TermosUso::POLITICA_PRIVACIDADE,
                'status' => TermosUso::ATIVO,
            ])
            ->orderBy('id DESC')
            ->one();

        if (!$termoUso) {
            $acceptTermsInfo[] = [
                'terms_accepted' => true,
                'terms_version' => '',              
                'privacy_policy_version' => '',
            ];
        } else {

            $usuarioAceitouTermoUso = TermosUsoAceiteUsuario::find()
                ->where([
                    'termos_uso_id' => $termoUso->id,
                    'user_ceon_id' => $user->id,
                ])
                ->one();

            $acceptTermsInfo[] = [
                'terms_accepted' => $usuarioAceitouTermoUso ? true : false,
                'terms_version' => $termoUso->id ?? '',              
                'privacy_policy_version' => $politicaPrivacidade->id ?? '',
            ];
        }




        Yii::$app->response->statusCode = 200;
        return [
            'access_token' => (string) $token,
            'refresh_token' => $refreshToken['urf_token'],
            'token_type' => 'JWT',
            'user_info' => [
                'recursos' => $recursos,
                'nome' => $user->name,
            ],
            'enterprises_info' => $enterprisesInfo,
            'accept_terms_info' => $acceptTermsInfo,

        ];
    }

    public function actionRefreshToken()
    {


        $headers = Yii::$app->request->headers;

        $refreshToken = $headers->get('refresh_token');

        if (!$refreshToken) {
            Yii::$app->response->statusCode = 401;
            return [
                'mensagem' => 'Falta parâmetro obrigatório: refresh token',
            ];
        }

        $userRefreshToken = \app\models\UserRefreshToken::findOne(['urf_token' => $refreshToken]);

        $user = \app\models\User::find()
            ->where([
                // refresh token inexistente: no 7.4 lia null (notice silenciado) e respondia 401
                'id' => $userRefreshToken->urf_user_id ?? null,
                'logado' => User::LOGADO_SIM,
                'status' => User::STATUS_ACTIVE,
            ])
            ->one();

        if (!$user) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '3 - Usuário ou senha inválidos',
            ];
        }

        //Buscando apenas as informações importantes do usuário
        $userObject = json_decode(json_encode($user['attributes']), false);

        try {
            $token = $this->generateJwt($userObject);
        } catch (Exception $e) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '5 -Erro ao gerar o JWT: ' . $e->getMessage()
            ];
        }

        UserRefreshToken::deleteAll(['urf_user_id' => $user->id]);

        try {
            $refreshToken = $this->generateRefreshToken($userObject);
        } catch (Exception $e) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => '6 - Erro ao gerar o refresh token JWT: ' . $e->getMessage()
            ];
        }

        Yii::$app->response->statusCode = 200;
        return [
            'access_token' => (string) $token,
            'refresh_token' => $refreshToken['urf_token'],
            'token_type' => 'JWT',
        ];
    }

    private function generateJwt(stdClass $user)
    {
        $jwt = Yii::$app->jwt;
        $signer = $jwt->getSigner('HS256');
        $key = $jwt->getKey();

        $jwtParams = Yii::$app->params['jwt'];

        // lcobucci/jwt 4.x: issuedAt/expiresAt exigem DateTimeImmutable
        $now = new \DateTimeImmutable();
        $timeExpire = $now->modify('+' . (60 * 60) . ' seconds'); // 1 hora, como no codigo anterior

        return $jwt->getBuilder()
            ->issuedBy($jwtParams['issuer'])
            ->permittedFor($jwtParams['audience'])
            ->identifiedBy($jwtParams['id'])
            ->issuedAt($now)
            ->expiresAt($timeExpire)
            ->withClaim('uid', $user->id)
            ->getToken($signer, $key);
    }

    /**
     * @throws yii\base\Exception
     */
    private function generateRefreshToken(stdClass $user, \app\models\User $impersonator = null): \app\models\UserRefreshToken
    {
        $refreshToken = Yii::$app->security->generateRandomString(200);

        // TODO: Don't always regenerate - you could reuse existing one if user already has one with same IP and user agent
        $userRefreshToken = new UserRefreshToken(
            [
                'urf_user_id' => $user->id,
                'urf_token' => $refreshToken,
                'urf_ip' => Yii::$app->request->userIP,
                'urf_user_agent' => Yii::$app->request->userAgent,
                'urf_created' => gmdate('Y-m-d H:i:s'),
            ]
        );

        if (!$userRefreshToken->save()) {
            throw new \yii\web\ServerErrorHttpException('Failed to save the refresh token: ' . implode('; ', $userRefreshToken->getErrorSummary(true)));
        }

        // Send the refresh-token to the user in a HttpOnly cookie that Javascript can never read and that's limited by path
        Yii::$app->response->cookies->add(new \yii\web\Cookie([
            'name' => 'refresh-token',
            'value' => $refreshToken,
            'httpOnly' => true,
            'sameSite' => 'none',
            'secure' => true,
            'path' => '/auth/refresh-token',  //endpoint URI for renewing the JWT token using this refresh-token, or deleting refresh-token
        ]));


        return $userRefreshToken;
    }
    public function actionValidaUsuario()
    {

        //Buscando valores da requisição
        $post = Yii::$app->request->post();


        //Verifica se preencheu usuário
        if (!isset($post['usuario'])) {
            return [
                'status' => 400,
                'message' => '1 - Erro nos parâmetros de entrada (usuario)'
            ];
        }

        //Verifica se preencheu senha
        if (!isset($post['senha'])) {
            return [
                'status' => 400,
                'message' => '2 - Erro nos parâmetros de entrada (senha)'
            ];
        }

        $usuario = $post['usuario'];
        $password = $post['senha'];
        $acessos = [];

        $user = \app\models\User::find()
            ->where([
                'username' => $usuario,
                'status' => User::STATUS_ACTIVE,
            ])
            ->one();

        if (!$user) {
            return [
                'status' => 400,
                'message' => '3 - Usuário não encontrado',
            ];
        }


        if (\Yii::$app->security->validatePassword($password, $user->password_hash)) {

            \Yii::$app->user->setIdentity($user);
            $roles = \Yii::$app->authManager->getRolesByUser(\Yii::$app->user->id);
            foreach ($roles as $k => $v) {
                $acessos[] = $k;
            }
        } else {
            return [
                'status' => 400,
                'message' => '4 - Usuário ou senha errados',
            ];
        }


        return [
            'status' => 200,
            'message' => 'Validado',
        ];
    }

    public function actionLogout()
    {

        $user = Yii::$app->user->identity;

        $user->logado = User::LOGADO_NAO;

        if (!$user->save()) {

            Yii::$app->response->statusCode = 401;

            return [
                'mensagem' => 'Erro ao deslogar o usuário',
            ];
        }

        Yii::$app->response->statusCode = 200;

        return [
            'mensagem' => 'Usuário deslogado com sucesso',
        ];
    }

    public function actionSelf()
    {

        $user = Yii::$app->user->identity;

        if (!$user) {

            Yii::$app->response->statusCode = 404;

            return [
                'mensagem' => 'Usuário não encontrado',
            ];
        }

        Yii::$app->response->statusCode = 200;

        return [
            'id' => $user->id,
            'username' => $user->username,
            'cpf' => $user->username,
            'email' => $user->email,
        ];
    }

    public function actionForgotPassword()
    {

        $post = Yii::$app->request->post();

        if (!isset($post['email'])) {
            Yii::$app->response->statusCode = 401;
            return [
                'mensagem' => 'Falta parâmetro obrigatório: email',
            ];
        }

        $email = $post['email'];


        $validator = new EmailValidator();

        if (!$validator->validate($email, $error)) {
            Yii::$app->response->statusCode = 422;
            return [
                'mensagem' => 'E-mail inválido',
            ];
        }

        $user = User::find()
            ->where([
                'email' => $email,
                'status' => User::ATIVO,
            ])
            ->one();

        if (!$user) {

            Yii::$app->response->statusCode = 404;

            return [
                'mensagem' => 'Usuário não encontrado',
            ];
        }



        $user->generatePasswordResetToken();
        $user->save();
        $token = $user->password_reset_token;
        $url = '<a href="https://homologacao.eletrobras.accia.com.br/user/recovery?token='.$token.'">' . Url::toRoute(['/user/recovery', 'token' => $token], true) . '</a>';

        $dados = [];
        $dados['user'] = $user;
        $dados['url'] = $url;

        EmailTemplate::enviaEmail(EmailTemplate::TIPO_RECUPERAR_SENHA, $dados, $user->email, 'Recuperação de senha da plataforma ' . getenv('NOME_PLATAFORMA'));

        Yii::$app->response->statusCode = 200;
        return [
            'mensagem' => "Email enviado. Acesse sua conta de email para instruções",
        ];
    }

    public function actionChangePassword()
    {

        $user = Yii::$app->user->identity;

        if (!$user) {

            Yii::$app->response->statusCode = 404;

            return [
                'mensagem' => 'Usuário não encontrado',
            ];
        }

        $post = Yii::$app->request->post();

        if (!isset($post['oldPassword'])) {
            Yii::$app->response->statusCode = 400;
            return [
                'mensagem' => 'Falta parâmetro obrigatório: Senha Atual',
            ];
        }

        if (!isset($post['newPassword'])) {
            Yii::$app->response->statusCode = 400;
            return [
                'mensagem' => 'Falta parâmetro obrigatório: Nova Senha',
            ];
        }

        $senhaAtual = $post['oldPassword'];
        $senhaNova = $post['newPassword'];

        $user->password_confirm = $senhaNova;
        $user->password_raw = $senhaNova;

        $login = new LoginForm();
        $login->username = $user->username;
        $login->password = $senhaAtual;

        if (!$login->validate()) {
            Yii::$app->response->statusCode = 422;
            return [
                'mensagem' => '1 - Erro ao atualizar a senha, motivo: ' . json_encode($login->getErrors()),
            ];
        }

        if (!$login->login()) {
            Yii::$app->response->statusCode = 422;
            return [
                'mensagem' => '2 - Erro ao atualizar a senha, motivo: ' . json_encode($login->getErrors()),
            ];
        }

        if (!$user->validate()) {

            Yii::$app->response->statusCode = 422;

            $erros = "  ";

            foreach ($user->getErrors() as $k => $v) {
                foreach ($v as $x => $y) {
                    $erros .= $y . ", ";
                }
            }

            $erros = substr($erros, 0, -2);
            return [
                'mensagem' => '3 - Erro ao atualizar a senha, motivo:' . $erros,
            ];
        }

        $user->setPassword($user->password_raw);
        $user->generateAuthKey();

        if (!$user->save()) {

            Yii::$app->response->statusCode = 422;
            return [
                'mensagem' => '4 - Erro ao atualizar a senha, motivo: ' . json_encode($user->getErrors()),
            ];
        }

        Yii::$app->response->statusCode = 200;
        return [
            'mensagem' => "Senha alterada com sucesso",
        ];
    }
}
