<?php namespace RVR\Controller;

use DateTime;
use RVR\Finance\BalanceCalculator;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\CurrencyExchangeRate;
use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityRepository;
use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CurrencyExchangeRateRepository;
use RVR\Repository\CurrencyRepository;
use RVR\Repository\EventRepository;
use RVR\Repository\TransactionRepository;
use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;

class CommunityController implements IAuthenticationRequired
{
    private UserRepository $userRepository;

    private CommunityRepository $communityRepository;

    private CommunityMemberRepository $communityMemberRepository;

    private CurrencyRepository $currencyRepository;

    private CurrencyExchangeRateRepository $currencyExchangeRatesRepository;

    private TransactionRepository $transactionRepository;

    private EventRepository $eventRepository;

    public function __construct()
    {
        $this->userRepository = new UserRepository();
        $this->communityRepository = new CommunityRepository();
        $this->communityMemberRepository = new CommunityMemberRepository();
        $this->currencyRepository = new CurrencyRepository();
        $this->currencyExchangeRatesRepository = new CurrencyExchangeRateRepository();
        $this->transactionRepository = new TransactionRepository();
        $this->eventRepository = new EventRepository();
    }

    public function isAuthenticationRequired(): bool
    {
        return true;
    }

    public function getCommunityHome(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), false, $community, $ownCommunityMember)) {
            return null;
        }

        \Container::$persistentDataManager->loadRelationsFromDb($community, false, ['main_currency']);

        $balanceCalculator = new BalanceCalculator($community);
        $debts = $balanceCalculator->calculate();
        $debtItems = [];
        $debtBalance = 0.0;
        $outstandingItems = [];
        $outstandingBalance = 0.0;
        foreach ($debts as $debt) {
            if ($debt['payer']->getId() === \Container::$request->user()->getUniqueId()) {
                $debtBalance += $debt['amount'];
                $debtItems[] = $debt;
            }
            if ($debt['payee']->getId() === \Container::$request->user()->getUniqueId()) {
                $outstandingBalance += $debt['amount'];
                $outstandingItems[] = $debt;
            }
        }
        $balance = $outstandingBalance - $debtBalance;

        return new HtmlContent('communities/community', [
            'community' => $community,
            'upcomingEvents' => iterator_to_array($this->eventRepository->getUpcomingByCommunity($community, new DateTime(), 3)),
            'debtItems' => $debtItems,
            'debtBalance' => $debtBalance,
            'outstandingItems' => $outstandingItems,
            'outstandingBalance' => $outstandingBalance,
            'balance' => $balance,
            'editPermission' => $ownCommunityMember->getOwner()
        ]);
    }

    public function getCommunitySettings(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), false, $community, $ownCommunityMember)) {
            return null;
        }

        return new HtmlContent('communities/community_settings', [
            'community' => $community,
            'members' => $this->getMembers($community),
            'currencies' => $this->getCurrencies($community),
            'editPermission' => $ownCommunityMember->getOwner()
        ]);
    }

    public function getCommunityNew(): IContent
    {
        return new HtmlContent('communities/community_edit');
    }

    public function getCommunityEdit(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        return new HtmlContent('communities/community_edit', [
            'community' => $community
        ]);
    }

    public function saveCommunity(): ?IContent
    {
        $name = \Container::$request->post('name');
        if (strlen($name) === 0) {
            return new JsonContent([
                'error' => ['errorText' => 'Please fill all required fields!']
            ]);
        }

        $communitySlug = \Container::$request->query('communitySlug');
        if ($communitySlug){
            if (!$this->checkPermission($communitySlug, true, $community, $ownCommunityMember)) {
                return null;
            }
        } else {
            $mainCurrencyCode = \Container::$request->post('main_currency_code');
            $mainCurrencyRoundDigits = \Container::$request->post('main_currency_round_digits');
            if (strlen($mainCurrencyCode) === 0 || strlen($mainCurrencyCode) > 3 || $mainCurrencyRoundDigits < 0 || $mainCurrencyRoundDigits > 9) {
                return new JsonContent([
                    'error' => ['errorText' => 'Please fill all required fields!']
                ]);
            }

            $community = new Community();
            $community->setCreatedDate(new DateTime());
        }

        $community->setName($name);
        \Container::$persistentDataManager->saveToDb($community);

        if (!$communitySlug) {
            /**
            * @var User $user
            */
            $user = \Container::$request->user();

            $communityMember = new CommunityMember();
            $communityMember->setCommunity($community);
            $communityMember->setUser($user);
            $communityMember->setOwner(true);
            \Container::$persistentDataManager->saveToDb($communityMember);

            $mainCurrency = new Currency();
            $mainCurrency->setCommunity($community);
            $mainCurrency->setCode($mainCurrencyCode);
            $mainCurrency->setRoundDigits($mainCurrencyRoundDigits);
            \Container::$persistentDataManager->saveToDb($mainCurrency);

            $community->setMainCurrency($mainCurrency);
            \Container::$persistentDataManager->saveToDb($community);
        }

        return new JsonContent([
            'redirect' => ['target' => \Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()])]
        ]);
    }

    public function deleteCommunity(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        if ($this->transactionRepository->countAllByCommunity($community) > 0) {
            return new JsonContent([
                'error' => ['errorText' => 'There are transactions for this community!']
            ]);
        }

        if ($this->eventRepository->countAllByCommunity($community) > 0) {
            return new JsonContent([
                'error' => ['errorText' => 'There are events for this community!']
            ]);
        }

        foreach ($this->communityMemberRepository->getAllByCommunity($community) as $communityMember) {
            \Container::$persistentDataManager->deleteFromDb($communityMember);
        }

        $community->setMainCurrencyId(null);
        \Container::$persistentDataManager->saveToDb($community);

        foreach ($this->currencyRepository->getAllByCommunity($community) as $currency) {
            foreach ($this->currencyExchangeRatesRepository->getAllByCurrency($currency) as $currencyExchangeRate) {
                \Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);
            }
            \Container::$persistentDataManager->deleteFromDb($currency);
        }

        \Container::$persistentDataManager->deleteFromDb($community);

        return new JsonContent(['success' => true]);
    }

    public function getMembersEdit(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        return new HtmlContent('communities/community_members', [
            'community' => $community,
            'members' => $this->getMembers($community)
        ]);
    }

    public function saveMember(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        $communityMemberId = \Container::$request->query('community_member_id');
        if ($communityMemberId) {
            $communityMember = $this->communityMemberRepository->getById($communityMemberId);
            if ($communityMember->getUserId() === $ownCommunityMember->getUserId()) {
                return new JsonContent([
                    'error' => ['errorText' => 'Own user cannot be edited.']
                ]);
            }
        } else {
            if ($this->transactionRepository->isAnyCommon()) {
                return new JsonContent([
                    'error' => ['errorText' => 'There are transactions with common payee!']
                ]);
            }

            $user = $this->userRepository->getById(\Container::$request->post('user_id'));
            if ($this->communityMemberRepository->getByCommunityAndUser($community, $user) !== null) {
                return new JsonContent([
                    'error' => ['errorText' => 'This user is already a member of this community.']
                ]);
            }

            $communityMember = new CommunityMember();
            $communityMember->setCommunity($community);
            $communityMember->setUser($user);
        }

        $communityMember->setOwner((bool)\Container::$request->post('owner'));
        \Container::$persistentDataManager->saveToDb($communityMember);

        return new JsonContent(['success' => true]);
    }

    public function deleteMember(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        $communityMember = $this->communityMemberRepository->getById(\Container::$request->query('community_member_id'));
        if ($communityMember->getUserId() === \Container::$request->user()->getUniqueId()) {
            return new JsonContent([
                'error' => ['errorText' => 'Own user cannot be deleted.']
            ]);
        }

        \Container::$persistentDataManager->loadRelationsFromDb($communityMember, false, ['user']);
        if ($this->transactionRepository->isAnyForUser($communityMember->getUser())) {
            return new JsonContent([
                'error' => ['errorText' => 'There are transactions where the member is payer or payee!']
            ]);
        }

        \Container::$persistentDataManager->deleteFromDb($communityMember);

        return new JsonContent(['success' => true]);
    }

    public function getCurrenciesEdit(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        return new HtmlContent('communities/community_currencies', [
            'community' => $community,
            'currencies' => $this->getCurrencies($community)
        ]);
    }

    public function saveCurrency(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        $code = \Container::$request->post('code');
        $roundDigits = (int)\Container::$request->post('round_digits');
        if (strlen($code) === 0 || strlen($code) > 3 || $roundDigits < 0 || $roundDigits > 9) {
            return new JsonContent([
                'error' => ['errorText' => 'Please fill all required fields!']
            ]);
        }

        $currencyId = \Container::$request->query('currency_id');
        if ($currencyId){
            $currency = $this->currencyRepository->getById($currencyId);
        } else {
            $currency = new Currency();
            $currency->setCommunity($community);
        }

        $existingCurrency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, $code);
        if ($existingCurrency !== null && $currency->getId() !== $existingCurrency->getId()) {
            return new JsonContent([
                'error' => ['errorText' => 'A currency with the same code exists for this community.']
            ]);
        }

        $currency->setCode($code);
        $currency->setRoundDigits($roundDigits);
        \Container::$persistentDataManager->saveToDb($currency);

        return new JsonContent(['success' => true]);
    }

    public function deleteCurrency(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        $currency = $this->currencyRepository->getById(\Container::$request->query('currency_id'));
        if ($currency->getId() === $community->getMainCurrencyId()) {
            return null;
        }

        if ($this->transactionRepository->isAnyForCurrency($currency)) {
            return new JsonContent([
                'error' => ['errorText' => 'There are transactions with this currency!']
            ]);
        }

        foreach ($this->currencyExchangeRatesRepository->getAllByCurrency($currency) as $currencyExchangeRate) {
            \Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);
        }

        \Container::$persistentDataManager->deleteFromDb($currency);

        return new JsonContent(['success' => true]);
    }

    public function getCurrencyExchangeRates(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        $currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
        if ($currency === null || $currency->getId() === $community->getMainCurrencyId()) {
            return null;
        }

        $currencyExchangeRates = $this->currencyExchangeRatesRepository->getAllByCurrency($currency);

        return new HtmlContent('communities/currency_exchange_rates', [
            'community' => $community,
            'currency' => $currency,
            'currencyExchangeRates' => $currencyExchangeRates,
            'editPermission' => $ownCommunityMember->getOwner()
        ]);
    }

    public function saveCurrencyExchangeRate(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        $currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
        if ($currency === null) {
            return null;
        }

        $exchangeRate = (float)\Container::$request->post('exchange_rate');
        if ($exchangeRate < 0) {
            return new JsonContent([
                'error' => ['errorText' => 'Please fill all required fields!']
            ]);
        }

        $currencyExchangeRateId = \Container::$request->query('currency_exchange_rate_id');
        if ($currencyExchangeRateId){
            $currencyExchangeRate = $this->currencyExchangeRatesRepository->getById($currencyExchangeRateId);
        } else {
            $currencyExchangeRate = new CurrencyExchangeRate();
            $currencyExchangeRate->setCurrency($currency);
        }

        $currencyExchangeRate->setExchangeRate($exchangeRate);
        $currencyExchangeRate->setValidFromDate(new DateTime(\Container::$request->post('valid_from')));
        \Container::$persistentDataManager->saveToDb($currencyExchangeRate);

        return new JsonContent(['success' => true]);
    }

    public function deleteCurrencyExchangeRate(): ?IContent
    {
        if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
            return null;
        }

        $currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
        if ($currency === null) {
            return null;
        }

        $currencyExchangeRate = $this->currencyExchangeRatesRepository->getById(\Container::$request->query('currency_exchange_rate_id'));
        \Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);

        return new JsonContent(['success' => true]);
    }

    private function getMembers(Community $community): array
    {
        $members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true, ['user']));
        usort($members, function($a, $b) {
            return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
        });
        return $members;
    }

    private function getCurrencies(Community $community): array
    {
        $currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community));
        usort($currencies, function($a, $b) {
            return strnatcmp($a->getCode(), $b->getCode());
        });
        usort($currencies, function($a, $b) use ($community) {
            return (int)($b->getId() === $community->getMainCurrencyId()) - (int)($a->getId() === $community->getMainCurrencyId());
        });
        return $currencies;
    }

    private function checkPermission(
        string $communitySlug,
        bool $needToBeOwner,
        ?Community &$community,
        ?CommunityMember &$ownCommunityMember): bool
    {
        $community = $this->communityRepository->getBySlug($communitySlug);
        if ($community === null) {
            return false;
        }

        /**
        * @var User $user
        */
        $user = \Container::$request->user();

        $ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $user);
        if ($ownCommunityMember === null || ($needToBeOwner && !$ownCommunityMember->getOwner())) {
            return false;
        }

        return true;
    }
}