<?php

namespace Shm\ShmBlueprints\Auth;

use Shm\Shm;
use Shm\ShmAuth\Auth;
use Shm\ShmDB\mDB;
use Shm\ShmUtils\I18n;
use Shm\ShmUtils\Response;

class ShmEmailSet extends ShmAuthBase
{
    private string $emailSetAuthCollection = '_email_set_auth';

    private int $timeLiveEmailSetCode = 60 * 15;

    public function make(): array
    {
        if (count($this->_authStructures) === 0) {
            throw new \Exception("No auth structures defined for Email Set");
        }

        return [
            'type' => Shm::boolean(),
            'description' => 'Установка email с подтверждением',
            'args' => Shm::structure([
                'email' => Shm::nonNull(Shm::string()),
                'code' => Shm::int(),
            ]),
            'resolve' => function ($root, $args) {
                Auth::authenticateOrThrow();

                $email = trim($args['email'] ?? '');
                $email = mb_strtolower($email);

                if (!$this->isEmail($email)) {
                    Response::validation(I18n::get('fill_required_fields'));
                }

                $this->forceProtect($email);

                $currentAuthStructure = $this->currentStructure();
                $emailField = $currentAuthStructure->findItemByType(Shm::email())?->key;

                if (!$emailField) {
                    Response::validation(I18n::get('auth_email_not_supported'));
                }

                $user = $currentAuthStructure->findOne([
                    '_id' => Auth::getAuthID(),
                ]);

                if (!$user) {
                    Response::validation(I18n::get('access_denied'));
                }

                if (isset($args['code'])) {
                    return $this->confirmEmail($email, (int) $args['code'], $emailField, $currentAuthStructure);
                }

                return $this->sendConfirmationCode($email, $user, $emailField);
            },
        ];
    }

    private function sendConfirmationCode(string $email, array $user, string $emailField)
    {
        $currentEmail = $user[$emailField] ?? null;
        if ($currentEmail && mb_strtolower(trim($currentEmail)) === $email) {
            Response::validation(I18n::get('email_same_as_current'));
        }

        $authUserAndStructure = $this->findAuthUserAndStructure(Shm::email(), $email, []);
        if ($authUserAndStructure) {
            [$existingUser] = $authUserAndStructure;
            if ((string) ($existingUser['_id'] ?? '') !== (string) Auth::getAuthID()) {
                Shm::error(I18n::get('email_already_used'));
            }
        }

        $collection = mDB::collection($this->emailSetAuthCollection);

        $findExisting = $collection->findOne(
            [
                'user_id' => mDB::id(Auth::getAuthID()),
                'email' => $email,
                'created_at' => ['$gt' => time() - $this->timeLiveEmailSetCode],
            ],
            [
                'sort' => ['_id' => -1],
            ]
        );

        if ($findExisting) {
            $code = (int) $findExisting->code;

            $collection->insertOne([
                'reSend' => true,
                'repeat_from' => $findExisting->created_at,
                'user_id' => mDB::id(Auth::getAuthID()),
                'auth_collection' => Auth::getAuthCollection(),
                'email' => $email,
                'code' => $code,
                'created_at' => time(),
                'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
            ]);
        } else {
            $code = rand(111111, 999999);

            $collection->insertOne([
                'user_id' => mDB::id(Auth::getAuthID()),
                'auth_collection' => Auth::getAuthCollection(),
                'email' => $email,
                'code' => (int) $code,
                'created_at' => time(),
                'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
            ]);
        }

        [$body, $subject] = ShmMailTpl::tplSetEmail($code);
        $this->sendEmail($email, $subject, $body);

        return null;
    }

    private function confirmEmail(string $email, int $code, string $emailField, $currentAuthStructure): bool
    {
        $findEmailSet = mDB::collection($this->emailSetAuthCollection)->findOne(
            [
                'user_id' => mDB::id(Auth::getAuthID()),
                'email' => $email,
                'code' => $code,
                'created_at' => ['$gt' => time() - $this->timeLiveEmailSetCode],
            ],
            [
                'sort' => ['_id' => -1],
            ]
        );

        if (!$findEmailSet) {
            Shm::error(I18n::get('wrong_code'));
        }

        foreach ($this->_authStructures as $authStructure) {
            $emailFieldLocal = $authStructure->findItemByType(Shm::email())?->key;
            if (!$emailFieldLocal) {
                continue;
            }

            $authStructure->updateOne(
                [
                    $emailFieldLocal => $email,
                ],
                [
                    '$unset' => [
                        $emailFieldLocal => 1,
                    ],
                ]
            );
        }

        $currentAuthStructure->updateOne(
            [
                '_id' => Auth::getAuthID(),
            ],
            [
                '$set' => [
                    $emailField => $email,
                ],
            ]
        );

        return true;
    }
}