<?php
declare(strict_types=1);

namespace App\Controller;

use App\Model\Entity\User;
use Cake\Datasource\EntityInterface;
use Cake\Http\Response;
use Cake\Routing\Router;
use Cake\Utility\Text;
use Cake\I18n\FrozenTime;
use Cake\Http\Exception\ForbiddenException;
use Cake\Http\Exception\NotFoundException;
use Cake\Log\Log;

class UsersController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('Authentication.Authentication');

        $this->Authentication->allowUnauthenticated([
            'login',
            'register',
            'forgotPassword',
            'resetPassword'
        ]);
    }

    // ================= LOGIN =================
    public function login()
    {
        $this->viewBuilder()->setTemplatePath('Pages');
        $this->viewBuilder()->setTemplate('login');

        $this->request->allowMethod(['get', 'post']);

        $redirectQuery = $this->request->getQuery('redirect');
        $this->set('redirectTarget', is_string($redirectQuery) ? $redirectQuery : '');

        if ($this->request->is('post')) {
            $data = $this->request->getData();
            $email = trim($data['email'] ?? '');

            Log::debug('LOGIN ATTEMPT EMAIL: ' . $email);

            // Find user first to check lock status and existence
            $user = $this->Users->find()
                ->where(['email' => $email])
                ->select(['id', 'email', 'password', 'role', 'failed_login_attempts', 'last_failed_login'])
                ->first();

            if (!$user) {
                Log::warning('LOGIN ATTEMPT WITH NON-EXISTENT EMAIL: ' . $email);
                if ($this->wantsJsonResponse()) {
                    return $this->jsonAuthResponse(['success' => false, 'message' => 'Invalid email or password.']);
                }
                $this->Flash->error('Invalid email or password.');

                return null;
            }

            // Check if account is locked
            if ($this->isAccountLocked($user)) {
                $unlockTime = $user->last_failed_login->addMinutes(30);
                $minutesLeft = max(1, $unlockTime->diffInMinutes(FrozenTime::now()));
                $lockMessage = "Your account is temporarily locked due to multiple failed attempts. Please try again in {$minutesLeft} minute(s).";
                if ($this->wantsJsonResponse()) {
                    return $this->jsonAuthResponse(['success' => false, 'locked' => true, 'message' => $lockMessage]);
                }
                $this->set('lockMessage', $lockMessage);

                return null;
            }

            // Proceed with Authentication
            $result = $this->Authentication->getResult();

            if ($result->isValid()) {
                // Reset failed attempts on successful login
                $user->failed_login_attempts = 0;
                $user->last_failed_login = null;
                $this->Users->save($user);

                Log::info('LOGIN SUCCESS: ' . $email);

                if ($user->role === 'admin') {
                    if ($this->wantsJsonResponse()) {
                        return $this->jsonAuthResponse([
                            'success' => true,
                            'redirect' => Router::url('/admin', true),
                        ]);
                    }

                    return $this->redirect('/admin');
                }

                $targetPath = $this->safeRedirectPath($data['redirect'] ?? null, '/');
                if ($this->wantsJsonResponse()) {
                    return $this->jsonAuthResponse([
                        'success' => true,
                        'redirect' => Router::url($targetPath, true),
                    ]);
                }

                return $this->redirect($targetPath);
            }

            // Handle failed login
            $this->handleFailedLogin($user);

            Log::warning('LOGIN FAILED for email: ' . $email . ' | Attempts: ' . ($user->failed_login_attempts));
            if ($this->wantsJsonResponse()) {
                return $this->jsonAuthResponse(['success' => false, 'message' => 'Invalid email or password.']);
            }
            $this->Flash->error('Invalid email or password.');
        }

        return null;
    }

    // ================= REGISTER =================
    public function register()
    {
        $this->viewBuilder()->setTemplatePath('Pages');
        $this->viewBuilder()->setTemplate('register');

        $redirectQuery = $this->request->getQuery('redirect');
        $this->set('redirectTarget', is_string($redirectQuery) ? $redirectQuery : '');

        $customersTable = $this->fetchTable('Customers');
        $user = $this->Users->newEmptyEntity();
        $customer = $customersTable->newEmptyEntity();

        if ($this->request->is('post')) {
            $data = $this->request->getData();

            Log::debug('REGISTER DATA: ' . json_encode($data));

            $afterLoginPath = $this->safeRedirectPath(
                $data['redirect'] ?? $this->request->getQuery('redirect'),
                '/'
            );

            $fullName = trim((string) ($data['full_name'] ?? ''));
            $phone = trim((string) ($data['phone'] ?? ''));
            $address = trim((string) ($data['address'] ?? ''));

            $customer = $customersTable->patchEntity($customer, [
                'name' => $fullName,
                'phone' => $phone,
                'address' => $address !== '' ? $address : null,
            ], ['validate' => false]);

            if (($data['password'] ?? '') !== ($data['confirm_password'] ?? '')) {
                if ($this->wantsJsonResponse()) {
                    return $this->jsonAuthResponse(['success' => false, 'message' => 'Passwords do not match.']);
                }
                $this->Flash->error('Passwords do not match.');
                $user = $this->Users->patchEntity($user, $data, [
                    'fields' => ['email', 'password'],
                    'validate' => false,
                ]);
                $this->set(compact('user', 'customer'));

                return null;
            }

            $user = $this->Users->patchEntity($user, $data, [
                'fields' => ['email', 'password'],
                'validate' => 'register',
            ]);
            $user->role = 'customer';

            if ($user->hasErrors()) {
                Log::error('REGISTER USER VALIDATION: ' . json_encode($user->getErrors()));
                if ($this->wantsJsonResponse()) {
                    return $this->jsonAuthResponse([
                        'success' => false,
                        'message' => $this->firstEntityErrorMessage($user),
                    ]);
                }
                $this->Flash->error('Please fix the errors below and try again.');
                $customer = $customersTable->patchEntity($customersTable->newEmptyEntity(), [
                    'name' => $fullName,
                    'phone' => $phone,
                    'address' => $address !== '' ? $address : null,
                ], ['validate' => false]);
                $this->set(compact('user', 'customer'));

                return null;
            }

            $customer = $customersTable->patchEntity($customersTable->newEmptyEntity(), [
                'name' => $fullName,
                'phone' => $phone,
                'address' => $address !== '' ? $address : null,
            ], [
                'validate' => 'register',
            ]);

            if ($customer->hasErrors()) {
                if ($this->wantsJsonResponse()) {
                    return $this->jsonAuthResponse([
                        'success' => false,
                        'message' => $this->firstEntityErrorMessage($customer),
                    ]);
                }
                $this->Flash->error('Please check your contact details.');
                $this->set(compact('user', 'customer'));

                return null;
            }

            $connection = $this->Users->getConnection();
            $saved = $connection->transactional(function () use ($user, $customersTable, $customer) {
                if (!$this->Users->save($user)) {
                    return false;
                }
                $customer->user_id = $user->id;

                return (bool) $customersTable->save($customer);
            });

            if ($saved) {
                if ($this->wantsJsonResponse()) {
                    return $this->jsonAuthResponse([
                        'success' => true,
                        'message' => 'Registration successful. Sign in below.',
                    ]);
                }
                $this->Flash->success('Registration successful. You can sign in now.');

                return $this->redirect([
                    'controller' => 'Users',
                    'action' => 'login',
                    '?' => ['redirect' => $afterLoginPath],
                ]);
            }

            Log::error('REGISTER FAILED');
            Log::error(json_encode($user->getErrors()));
            Log::error(json_encode($customer->getErrors()));
            if ($this->wantsJsonResponse()) {
                return $this->jsonAuthResponse([
                    'success' => false,
                    'message' => 'Registration could not be completed. Please try again.',
                ]);
            }
            $this->Flash->error('Registration could not be completed. Please try again.');
        }

        $this->set(compact('user', 'customer'));

        return null;
    }

    // ================= LOGOUT =================
    public function logout()
    {
        Log::debug('USER LOGOUT');
        $this->Authentication->logout();
        return $this->redirect(['action' => 'login']);
    }

    // ================= FORGOT PASSWORD =================
    public function forgotPassword()
    {
        $this->viewBuilder()->setTemplatePath('Pages');
        $this->viewBuilder()->setTemplate('forgot_password');

        if ($this->request->is('post')) {
            $email = $this->request->getData('email');

            Log::debug('FORGOT PASSWORD EMAIL: ' . $email);

            $user = $this->Users->findByEmail($email)->first();

            if ($user) {
                $user->nonce = Text::uuid();
                $user->nonce_expiry = FrozenTime::now()->addHours(1);

                $this->Users->save($user);

                Log::debug('RESET TOKEN GENERATED: ' . $user->nonce);

                $this->Flash->success('Reset link (demo): /users/resetPassword/' . $user->nonce);
            } else {
                Log::error('EMAIL NOT FOUND: ' . $email);
                $this->Flash->error('Email not found');
            }
        }
    }

    // ================= RESET PASSWORD =================
    public function resetPassword($token = null)
    {
        $this->viewBuilder()->setTemplatePath('Pages');
        $this->viewBuilder()->setTemplate('reset_password');

        Log::debug('RESET TOKEN: ' . $token);

        $user = $this->Users->find()
            ->where([
                'nonce' => $token,
                'nonce_expiry >' => FrozenTime::now()
            ])
            ->first();

        if (!$user) {
            Log::error('INVALID TOKEN');
            throw new NotFoundException('Invalid or expired token');
        }

        if ($this->request->is(['post', 'put'])) {
            $data = $this->request->getData();

            if ($data['password'] !== ($data['confirm_password'] ?? null)) {
                $this->Flash->error('Passwords do not match');
                return;
            }

            $user = $this->Users->patchEntity($user, $data);

            $user->nonce = null;
            $user->nonce_expiry = null;

            if ($this->Users->save($user)) {
                Log::debug('PASSWORD RESET SUCCESS');
                $this->Flash->success('Password updated successfully');
                return $this->redirect(['action' => 'login']);
            }

            Log::error('PASSWORD RESET FAILED');
            $this->Flash->error('Something went wrong');
        }

        $this->set(compact('user'));
    }

    // ================= ADMIN CRUD =================
    public function index()
    {
        $this->checkAdmin();
        $users = $this->paginate($this->Users);
        $this->set(compact('users'));
    }

    public function view($id = null)
    {
        $this->checkAdmin();
        $user = $this->Users->get($id, contain: ['Enquiries']);
        $this->set(compact('user'));
    }

    public function add()
    {
        $this->checkAdmin();
        $user = $this->Users->newEmptyEntity();

        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->getData());

            if ($this->Users->save($user)) {
                $this->Flash->success('User saved');
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error('Failed to save user');
        }
        $this->set(compact('user'));
    }

    public function edit($id = null)
    {
        $this->checkAdmin();
        $user = $this->Users->get($id);

        if ($this->request->is(['patch', 'post', 'put'])) {
            $user = $this->Users->patchEntity($user, $this->request->getData());

            if ($this->Users->save($user)) {
                $this->Flash->success('User updated');
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error('Update failed');
        }
        $this->set(compact('user'));
    }

    public function delete($id = null)
    {
        $this->checkAdmin();
        $this->request->allowMethod(['post', 'delete']);

        $user = $this->Users->get($id);

        if ($this->Users->delete($user)) {
            $this->Flash->success('User deleted');
        } else {
            $this->Flash->error('Delete failed');
        }

        return $this->redirect(['action' => 'index']);
    }

    // ================= PROFILE =================
    public function profile()
    {
        $user = $this->request->getAttribute('identity');

        $customersTable = $this->fetchTable('Customers');

        $customer = $customersTable->find()
            ->where(['user_id' => $user->id])
            ->first();

        if ($customer === null) {
            $customer = $customersTable->newEmptyEntity();
            $customer->user_id = $user->id;
        }

        $bookings = $this->fetchTable('Bookings')->find()
            ->where(['Bookings.user_id' => $user->id])
            ->contain(['Lessons', 'Payments'])
            ->order(['Bookings.created' => 'DESC'])
            ->limit(10)
            ->all();

        if ($this->request->is(['post', 'put'])) {
            $customer = $customersTable->patchEntity($customer, $this->request->getData());

            if ($customersTable->save($customer)) {
                $this->Flash->success('Profile updated');
            } else {
                $this->Flash->error('Update failed');
            }
        }

        $this->set(compact('customer', 'bookings'));
    }

    // ================= SECURITY HELPERS =================
    private function isAccountLocked(User $user): bool
    {
        if ($user->failed_login_attempts < 5) {
            return false;
        }

        if (!$user->last_failed_login) {
            return false;
        }

        $lockUntil = $user->last_failed_login->addMinutes(30);
        return FrozenTime::now()->lessThan($lockUntil);
    }

    private function handleFailedLogin(User $user): void
    {
        $current = (int) ($user->failed_login_attempts ?? 0);
        
        $user->failed_login_attempts = $current + 1;
        $user->last_failed_login     = FrozenTime::now();

        $this->Users->save($user);
    }
    private function checkAdmin()
    {
        $user = $this->request->getAttribute('identity');

        if (!$user || $user->role !== 'admin') {
            Log::error('UNAUTHORIZED ACCESS');
            throw new ForbiddenException('Access denied');
        }
    }

    private function wantsJsonResponse(): bool
    {
        $accept = $this->request->getHeaderLine('Accept');
        if (str_contains($accept, 'application/json')) {
            return true;
        }

        return $this->request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function jsonAuthResponse(array $payload, int $status = 200): Response
    {
        return $this->response
            ->withStatus($status)
            ->withType('application/json')
            ->withStringBody((string)json_encode($payload));
    }

    private function safeRedirectPath(?string $candidate, string $default = '/'): string
    {
        if ($candidate === null || $candidate === '') {
            return $default;
        }
        $candidate = trim(rawurldecode($candidate));
        if (!str_starts_with($candidate, '/') || str_starts_with($candidate, '//')) {
            return $default;
        }
        if (strlen($candidate) > 512 || str_contains($candidate, "\0")) {
            return $default;
        }

        return $candidate;
    }

    private function firstEntityErrorMessage(EntityInterface $entity): string
    {
        foreach ($entity->getErrors() as $msgs) {
            foreach ((array)$msgs as $m) {
                if (is_string($m)) {
                    return $m;
                }
                if (is_array($m)) {
                    $inner = reset($m);
                    if (is_string($inner)) {
                        return $inner;
                    }
                }
            }
        }

        return 'Please check your input and try again.';
    }
}