<?php

namespace App\Tests\Controller;

use App\Controller\ForgotPasswordController;
use App\Controller\HomeController;
use App\Controller\KeycloakController;
use App\Controller\SecurityController;
use App\Controller\SetPasswordController;
use App\Entity\User;
use App\Repository\UserRepository;
use App\Service\MailerService;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\OAuth2ClientInterface;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Twig\Environment;

class MainControllersTest extends TestCase
{
    private function createContainer(array $services = []): ContainerInterface
    {
        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnCallback(fn($id) => isset($services[$id]));
        $container->method('get')->willReturnCallback(fn($id) => $services[$id] ?? null);
        return $container;
    }

    private function setupController($controller, array $services = []): void
    {
        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);

        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect-url');

        $defaultServices = [
            'twig' => $this->createStub(Environment::class),
            'router' => $router,
            'security.authorization_checker' => $this->createStub(AuthorizationCheckerInterface::class),
            'security.token_storage' => $this->createStub(TokenStorageInterface::class),
            'request_stack' => $stack,
            'parameter_bag' => $this->createStub(\Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface::class),
        ];
        $controller->setContainer($this->createContainer(array_merge($defaultServices, $services)));
    }

    public function testSecurityLogout(): void
    {
        $controller = new SecurityController();
        $this->expectException(\LogicException::class);
        $controller->logout();
    }

    public function testKeycloakConnect(): void
    {
        $client = $this->createStub(OAuth2ClientInterface::class);
        $client->method('redirect')->willReturn(new \Symfony\Component\HttpFoundation\RedirectResponse('http://k'));
        $registry = $this->createStub(ClientRegistry::class);
        $registry->method('getClient')->willReturn($client);

        $controller = new KeycloakController();
        $this->assertSame('http://k', $controller->connect($registry)->getTargetUrl());
    }

    public function testKeycloakCheck(): void
    {
        $controller = new KeycloakController();
        $this->assertSame(200, $controller->check()->getStatusCode());
    }

    public function testHomeIndexNoUser(): void
    {
        $controller = new HomeController();
        $this->setupController($controller);
        $this->assertSame(200, $controller->index()->getStatusCode());
    }

    public function testHomeIndexWithEmploye(): void
    {
        $user = new User();
        $user->setEmail('admin@test.com');
        $user->setFirstName('A');
        $user->setLastName('B');
        $user->setPassword('h');
        $user->setRoles(['ROLE_EMPLOYE']);

        $token = $this->createStub(\Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class);
        $token->method('getUser')->willReturn($user);

        $tokenStorage = $this->createStub(TokenStorageInterface::class);
        $tokenStorage->method('getToken')->willReturn($token);

        $authChecker = $this->createStub(AuthorizationCheckerInterface::class);
        $authChecker->method('isGranted')->willReturnCallback(fn ($role) => \in_array($role, ['ROLE_EMPLOYE', 'ROLE_USER'], true));

        $controller = new HomeController();
        $this->setupController($controller, [
            'security.token_storage' => $tokenStorage,
            'security.authorization_checker' => $authChecker,
        ]);

        $response = $controller->index();
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testHomeIndexWithUserNoSpecificRole(): void
    {
        $user = new User();
        $user->setEmail('u@t.com');
        $user->setFirstName('U');
        $user->setLastName('T');
        $user->setPassword('h');

        $token = $this->createStub(\Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class);
        $token->method('getUser')->willReturn($user);

        $tokenStorage = $this->createStub(TokenStorageInterface::class);
        $tokenStorage->method('getToken')->willReturn($token);

        $authChecker = $this->createStub(AuthorizationCheckerInterface::class);
        $authChecker->method('isGranted')->willReturn(false);

        $controller = new HomeController();
        $this->setupController($controller, [
            'security.token_storage' => $tokenStorage,
            'security.authorization_checker' => $authChecker,
        ]);

        $response = $controller->index();
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testHomeIndexWithCustomer(): void
    {
        $user = new User();
        $user->setEmail('c@test.com');
        $user->setFirstName('C');
        $user->setLastName('D');
        $user->setPassword('h');
        $user->setRoles(['ROLE_CUSTOMER']);

        $token = $this->createStub(\Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class);
        $token->method('getUser')->willReturn($user);

        $tokenStorage = $this->createStub(TokenStorageInterface::class);
        $tokenStorage->method('getToken')->willReturn($token);

        $authChecker = $this->createStub(AuthorizationCheckerInterface::class);
        $authChecker->method('isGranted')->willReturnCallback(fn ($role) => \in_array($role, ['ROLE_CUSTOMER', 'ROLE_USER'], true));

        $controller = new HomeController();
        $this->setupController($controller, [
            'security.token_storage' => $tokenStorage,
            'security.authorization_checker' => $authChecker,
        ]);

        $response = $controller->index();
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSetPasswordSuccess(): void
    {
        $user = new User();
        $repo = $this->createStub(UserRepository::class);
        $repo->method('findOneBy')->willReturn($user);

        $hasher = $this->createStub(UserPasswordHasherInterface::class);
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('flush');

        $controller = new SetPasswordController();
        $this->setupController($controller);

        $request = new Request([], ['password' => 'newpassword8', 'password_confirm' => 'newpassword8']);
        $request->setMethod('POST');
        
        $response = $controller->index('token', $request, $repo, $hasher, $em);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testForgotPasswordFullFlow(): void
    {
        $user = new User();
        $repo = $this->createStub(UserRepository::class);
        $repo->method('findOneBy')->willReturn($user);

        $mailer = $this->createMock(MailerService::class);
        $em = $this->createMock(EntityManagerInterface::class);
        $hasher = $this->createStub(UserPasswordHasherInterface::class);
        $twig = $this->createStub(Environment::class);

        $controller = new ForgotPasswordController();
        $this->setupController($controller, ['twig' => $twig]);

        // 1. GET
        $request = new Request();
        $session = new Session(new MockArraySessionStorage());
        $request->setSession($session);
        $response = $controller->index($request, $repo, $mailer, $em, $hasher, $twig);
        $this->assertSame(200, $response->getStatusCode());

        // 2. POST send_code
        $request = new Request([], ['action' => 'send_code', 'email' => 't@t.com']);
        $request->setMethod('POST');
        $request->setSession($session);
        $mailer->expects($this->exactly(2))->method('sendEmail');
        $response = $controller->index($request, $repo, $mailer, $em, $hasher, $twig);
        $this->assertSame(200, $response->getStatusCode());
        $code = $session->get('reset_code');

        // 3. POST reset
        $request = new Request([], ['action' => 'reset', 'code' => $code, 'password' => 'newpassword8']);
        $request->setMethod('POST');
        $request->setSession($session);
        $em->expects($this->once())->method('flush');
        $response = $controller->index($request, $repo, $mailer, $em, $hasher, $twig);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testForgotPasswordExpiredCode(): void
    {
        $repo = $this->createStub(UserRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $hasher = $this->createStub(UserPasswordHasherInterface::class);
        $twig = $this->createStub(Environment::class);

        $controller = new ForgotPasswordController();
        $this->setupController($controller, ['twig' => $twig]);

        $session = new Session(new MockArraySessionStorage());
        $session->set('reset_code', 'abc123');
        $session->set('reset_email', 't@t.com');
        $session->set('reset_expires', time() - 100);

        $request = new Request([], ['action' => 'reset', 'code' => 'abc123', 'password' => 'newpassword8']);
        $request->setMethod('POST');
        $request->setSession($session);

        $response = $controller->index($request, $repo, $mailer, $em, $hasher, $twig);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testForgotPasswordWrongCode(): void
    {
        $repo = $this->createStub(UserRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $hasher = $this->createStub(UserPasswordHasherInterface::class);
        $twig = $this->createStub(Environment::class);

        $controller = new ForgotPasswordController();
        $this->setupController($controller, ['twig' => $twig]);

        $session = new Session(new MockArraySessionStorage());
        $session->set('reset_code', 'correct');
        $session->set('reset_email', 't@t.com');
        $session->set('reset_expires', time() + 600);

        $request = new Request([], ['action' => 'reset', 'code' => 'wrong', 'password' => 'newpassword8']);
        $request->setMethod('POST');
        $request->setSession($session);

        $response = $controller->index($request, $repo, $mailer, $em, $hasher, $twig);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testForgotPasswordShortPassword(): void
    {
        $repo = $this->createStub(UserRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $hasher = $this->createStub(UserPasswordHasherInterface::class);
        $twig = $this->createStub(Environment::class);

        $controller = new ForgotPasswordController();
        $this->setupController($controller, ['twig' => $twig]);

        $session = new Session(new MockArraySessionStorage());
        $session->set('reset_code', 'abc123');
        $session->set('reset_email', 't@t.com');
        $session->set('reset_expires', time() + 600);

        $request = new Request([], ['action' => 'reset', 'code' => 'abc123', 'password' => 'short']);
        $request->setMethod('POST');
        $request->setSession($session);

        $response = $controller->index($request, $repo, $mailer, $em, $hasher, $twig);
        $this->assertSame(200, $response->getStatusCode());
    }
}
