<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\BaseController;
use App\Core\Database;
use App\Core\Request;
use App\Core\Response;
use App\Helpers\AjaxResponse;
use App\Models\UserModel;
use App\Services\ActivityLogger;
use App\Services\PasswordPolicyService;
use App\Services\SessionService;
use App\Services\TwoFactorService;
use App\Services\UserService;

final class ProfileController extends BaseController
{
    public function show(Request $request): Response
    {
        $userId = $this->currentUserId();
        $user = (new UserModel())->findWithRelations($userId);
        $sessions = (new SessionService())->activeSessionsFor($userId);
        $preferences = $this->loadPreferences($userId);

        return $this->view('profile/show', [
            'title' => 'My Profile',
            'activeNav' => 'profile',
            'breadcrumbs' => [['Home', 'dashboard'], ['My Profile', 'profile']],
            'user' => $user,
            'sessions' => $sessions,
            'preferences' => $preferences,
            'two_factor_enabled' => (bool) $user['two_factor_enabled'],
        ]);
    }

    public function update(Request $request): Response
    {
        $data = $this->validateOrFail($request, [
            'first_name' => 'required|string|max:100',
            'last_name' => 'nullable|string|max:100',
            'phone' => 'nullable|string|max:30',
            'job_title' => 'nullable|string|max:120',
        ]);

        $userId = $this->currentUserId();
        (new UserModel())->update($userId, $data, $userId);

        ActivityLogger::record('user', $userId, 'profile_updated', 'Updated their own profile');

        return AjaxResponse::success(['user' => (new UserModel())->findWithRelations($userId)], 'Profile updated.');
    }

    public function updateEmail(Request $request): Response
    {
        $data = $this->validateOrFail($request, ['email' => 'required|email|max:191']);
        $userId = $this->currentUserId();
        $userModel = new UserModel();

        if ($userModel->emailTaken($data['email'], $userId)) {
            return AjaxResponse::validationError(['email' => ['That email is already in use.']]);
        }

        $user = $userModel->find($userId);
        (new \App\Services\EmailVerificationService())->sendVerification($userId, $user['email'], $data['email']);

        return AjaxResponse::success(null, 'Check your new email address for a verification link to confirm the change.');
    }

    public function changePassword(Request $request): Response
    {
        $data = $this->validateOrFail($request, [
            'current_password' => 'required|string',
            'password' => 'required|string|confirmed',
        ]);

        $userId = $this->currentUserId();
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT password_hash FROM users WHERE id = :id');
        $stmt->execute(['id' => $userId]);
        $currentHash = $stmt->fetchColumn();

        if (!password_verify($data['current_password'], $currentHash)) {
            return AjaxResponse::validationError(['current_password' => ['Your current password is incorrect.']]);
        }

        $policy = new PasswordPolicyService();
        $errors = $policy->validateComplexity($data['password']);
        if ($errors !== []) {
            return AjaxResponse::validationError(['password' => $errors]);
        }
        if ($policy->isReused($userId, $data['password'])) {
            return AjaxResponse::validationError(['password' => ['You cannot reuse a recent password.']]);
        }

        $policy->commit($userId, $data['password']);
        ActivityLogger::logAction('Password changed', 'security', 'user', $userId, $request);

        return AjaxResponse::success(null, 'Password changed successfully.');
    }

    public function uploadAvatar(Request $request): Response
    {
        $file = $request->file('avatar');
        if ($file === null) {
            return AjaxResponse::error('No file was uploaded.', 422);
        }

        $userId = $this->currentUserId();
        $result = (new UserService())->updateAvatar($userId, $file);

        if (!$result['success']) {
            return AjaxResponse::error('Upload failed.', 422, $result['errors']);
        }

        return AjaxResponse::success(['avatar_path' => $result['path']], 'Profile photo updated.');
    }

    public function updatePreferences(Request $request): Response
    {
        $preferences = $request->input('preferences', []);
        if (!is_array($preferences)) {
            return AjaxResponse::error('Invalid preferences payload.', 422);
        }

        $userId = $this->currentUserId();
        $pdo = Database::connection();
        $stmt = $pdo->prepare(
            'INSERT INTO user_preferences (user_id, pref_key, pref_value, updated_at)
             VALUES (:user_id, :key, :value, NOW())
             ON DUPLICATE KEY UPDATE pref_value = VALUES(pref_value), updated_at = NOW()'
        );

        foreach ($preferences as $key => $value) {
            $stmt->execute(['user_id' => $userId, 'key' => (string) $key, 'value' => (string) $value]);
        }

        return AjaxResponse::success(['preferences' => $this->loadPreferences($userId)], 'Preferences saved.');
    }

    // ---- Sessions ----

    public function listSessions(Request $request): Response
    {
        return AjaxResponse::success((new SessionService())->activeSessionsFor($this->currentUserId()));
    }

    public function revokeSession(Request $request, array $params): Response
    {
        $revoked = (new SessionService())->revoke($this->currentUserId(), $params['sessionId']);

        return $revoked
            ? AjaxResponse::success(null, 'Session revoked.')
            : AjaxResponse::error('Could not revoke that session.', 422);
    }

    public function revokeAllOtherSessions(Request $request): Response
    {
        $count = (new SessionService())->revokeAllExceptCurrent($this->currentUserId());
        ActivityLogger::logAction("Revoked {$count} other session(s)", 'security', 'user', $this->currentUserId(), $request);

        return AjaxResponse::success(['revoked' => $count], "Signed out of {$count} other session(s).");
    }

    // ---- Two-Factor Authentication ----

    public function twoFactorSetup(Request $request): Response
    {
        $twoFactor = new TwoFactorService();
        $secret = $twoFactor->generateSecret();
        $user = (new UserModel())->find($this->currentUserId());

        // Stash the pending secret in the session until confirmed with a
        // real code — never persisted to users.two_factor_secret until enable().
        \App\Core\Session::set('_pending_2fa_secret', $secret);

        return AjaxResponse::success([
            'secret' => $secret,
            'provisioning_uri' => $twoFactor->provisioningUri($secret, $user['email']),
        ]);
    }

    public function twoFactorConfirm(Request $request): Response
    {
        $data = $this->validateOrFail($request, ['code' => 'required|string']);
        $secret = \App\Core\Session::get('_pending_2fa_secret');

        if (!is_string($secret)) {
            return AjaxResponse::error('No pending two-factor setup found. Please restart setup.', 422);
        }

        $twoFactor = new TwoFactorService();
        if (!$twoFactor->verify($secret, $data['code'])) {
            return AjaxResponse::error('That code is incorrect. Please try again.', 422);
        }

        $userId = $this->currentUserId();
        $twoFactor->enable($userId, $secret);
        $recoveryCodes = $twoFactor->generateRecoveryCodes($userId);
        \App\Core\Session::remove('_pending_2fa_secret');

        ActivityLogger::logAction('Two-factor authentication enabled', 'security', 'user', $userId, $request);

        return AjaxResponse::success(['recovery_codes' => $recoveryCodes], 'Two-factor authentication is now enabled. Save these recovery codes somewhere safe.');
    }

    public function twoFactorDisable(Request $request): Response
    {
        $data = $this->validateOrFail($request, ['password' => 'required|string']);
        $userId = $this->currentUserId();

        $pdo = Database::connection();
        $hash = $pdo->prepare('SELECT password_hash FROM users WHERE id = :id');
        $hash->execute(['id' => $userId]);

        if (!password_verify($data['password'], $hash->fetchColumn())) {
            return AjaxResponse::validationError(['password' => ['Incorrect password.']]);
        }

        (new TwoFactorService())->disable($userId);
        ActivityLogger::logAction('Two-factor authentication disabled', 'security', 'user', $userId, $request);

        return AjaxResponse::success(null, 'Two-factor authentication has been disabled.');
    }

    private function loadPreferences(int $userId): array
    {
        $stmt = Database::connection()->prepare('SELECT pref_key, pref_value FROM user_preferences WHERE user_id = :id');
        $stmt->execute(['id' => $userId]);

        return $stmt->fetchAll(\PDO::FETCH_KEY_PAIR);
    }
}
