<?php

namespace App\Controllers;

use App\Libraries\TemporaryImageUpload;
use App\Models\UserModel;
use Config\Database;
use RuntimeException;

class UserController extends BaseController
{
    private UserModel $userModel;
    private TemporaryImageUpload $temporaryImageUpload;
    private string $uploadPath;

    public function __construct()
    {
        $this->userModel           = new UserModel();
        $this->temporaryImageUpload = new TemporaryImageUpload();
        $this->uploadPath          = FCPATH . 'uploads/users';
    }

    public function index()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/dashboard', 'Akses ditolak. Data user hanya dapat diakses oleh administrator.')) {
            return $redirect;
        }

        $users = $this->userModel
            ->select('user.*')
            ->select('(SELECT COUNT(*) FROM pembelian WHERE pembelian.user_id = user.id) AS total_pembelian', false)
            ->select('(SELECT COUNT(*) FROM penjualan WHERE penjualan.user_id = user.id) AS total_penjualan', false)
            ->select('(SELECT COUNT(*) FROM service WHERE service.user_id = user.id) AS total_service', false)
            ->select('(SELECT COUNT(*) FROM stok_penyesuaian WHERE stok_penyesuaian.user_id = user.id OR stok_penyesuaian.dibatalkan_oleh = user.id) AS total_stok_penyesuaian', false)
            ->orderBy('id', 'DESC')
            ->findAll();

        return view('admin/users/index', [
            'users'     => $users,
            'userName'  => session()->get('userName'),
            'userEmail' => session()->get('userEmail'),
            'userRole'  => session()->get('userRole'),
        ]);
    }

    public function create()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/users')) {
            return $redirect;
        }

        return view('admin/users/create', [
            'userName'  => session()->get('userName'),
            'userEmail' => session()->get('userEmail'),
            'userRole'  => session()->get('userRole'),
        ]);
    }

    public function store()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/users')) {
            return $redirect;
        }

        $rules = [
            'name'      => 'required|min_length[3]',
            'email'     => 'required|valid_email|is_unique[user.email]',
            'password'  => 'required|min_length[6]',
            'role'      => 'required|in_list[administrator,kasir]',
            'is_active' => 'required|in_list[0,1]',
        ];

        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        try {
            $photoName = $this->moveTemporaryPhoto();
        } catch (RuntimeException $exception) {
            return redirect()->back()->withInput()->with('errors', [
                'photo' => $exception->getMessage(),
            ]);
        }

        $this->userModel->insert([
            'name'      => $this->request->getPost('name'),
            'email'     => $this->request->getPost('email'),
            'photo'     => $photoName,
            'password'  => password_hash((string) $this->request->getPost('password'), PASSWORD_DEFAULT),
            'role'      => $this->request->getPost('role'),
            'is_active' => $this->request->getPost('is_active'),
        ]);

        return redirect()->to('/admin/users')->with('success', 'Data user berhasil ditambahkan.');
    }

    public function edit(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/users')) {
            return $redirect;
        }

        $user = $this->userModel->find($id);

        if (! $user) {
            return redirect()->to('/admin/users')->with('error', 'Data user tidak ditemukan.');
        }

        return view('admin/users/edit', [
            'user'      => $user,
            'userName'  => session()->get('userName'),
            'userEmail' => session()->get('userEmail'),
            'userRole'  => session()->get('userRole'),
        ]);
    }

    public function update(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/users')) {
            return $redirect;
        }

        $user = $this->userModel->find($id);

        if (! $user) {
            return redirect()->to('/admin/users')->with('error', 'Data user tidak ditemukan.');
        }

        $rules = [
            'name'      => 'required|min_length[3]',
            'email'     => 'required|valid_email|is_unique[user.email,id,' . $id . ']',
            'password'  => 'permit_empty|min_length[6]',
            'role'      => 'required|in_list[administrator,kasir]',
            'is_active' => 'required|in_list[0,1]',
        ];

        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        $newRole = (string) $this->request->getPost('role');
        $newStatus = (string) $this->request->getPost('is_active');

        if ($this->wouldRemoveLastActiveAdministrator($user, $newRole, $newStatus)) {
            return redirect()->back()->withInput()->with('error', 'Minimal harus ada 1 administrator aktif.');
        }

        $data = [
            'name'      => $this->request->getPost('name'),
            'email'     => $this->request->getPost('email'),
            'role'      => $newRole,
            'is_active' => $newStatus,
        ];

        try {
            $photoName = $this->moveTemporaryPhoto();
        } catch (RuntimeException $exception) {
            return redirect()->back()->withInput()->with('errors', [
                'photo' => $exception->getMessage(),
            ]);
        }

        if ($photoName !== null) {
            $data['photo'] = $photoName;
            $this->deletePhoto($user['photo'] ?? null);
        }

        $newPassword = (string) $this->request->getPost('password');

        if ($newPassword !== '') {
            $data['password'] = password_hash($newPassword, PASSWORD_DEFAULT);
        }

        $this->userModel->update($id, $data);

        return redirect()->to('/admin/users')->with('success', 'Data user berhasil diperbarui.');
    }

    public function delete(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/users')) {
            return $redirect;
        }

        if ((int) session()->get('userId') === $id) {
            return redirect()->to('/admin/users')->with('error', 'Akun yang sedang login tidak boleh dihapus.');
        }

        $user = $this->userModel->find($id);

        if (! $user) {
            return redirect()->to('/admin/users')->with('error', 'Data user tidak ditemukan.');
        }

        if ($this->wouldRemoveLastActiveAdministrator($user, null, null)) {
            return redirect()->to('/admin/users')->with('error', 'Minimal harus ada 1 administrator aktif.');
        }

        $usage = $this->userUsage($id);

        if (array_sum($usage) > 0) {
            return redirect()->to('/admin/users')->with(
                'error',
                'Data user tidak dapat dihapus karena sudah terhubung ke transaksi pembelian, penjualan, service, atau penyesuaian stok.'
            );
        }

        $this->deletePhoto($user['photo'] ?? null);
        $this->userModel->delete($id);

        return redirect()->to('/admin/users')->with('success', 'Data user berhasil dihapus.');
    }

    public function profile()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        $user = $this->userModel->find((int) session()->get('userId'));

        if (! $user) {
            session()->destroy();

            return redirect()->to('/login')->with('error', 'Data user tidak ditemukan. Silakan login ulang.');
        }

        return view('admin/users/profile', [
            'user'      => $user,
            'userName'  => session()->get('userName'),
            'userEmail' => session()->get('userEmail'),
            'userRole'  => session()->get('userRole'),
        ]);
    }

    public function updateProfile()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        $userId = (int) session()->get('userId');
        $user   = $this->userModel->find($userId);

        if (! $user) {
            session()->destroy();

            return redirect()->to('/login')->with('error', 'Data user tidak ditemukan. Silakan login ulang.');
        }

        $rules = [
            'name'  => 'required|min_length[3]',
            'email' => 'required|valid_email|is_unique[user.email,id,' . $userId . ']',
        ];

        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        $data = [
            'name'  => $this->request->getPost('name'),
            'email' => $this->request->getPost('email'),
        ];

        try {
            $photoName = $this->moveTemporaryPhoto();
        } catch (RuntimeException $exception) {
            return redirect()->back()->withInput()->with('errors', [
                'photo' => $exception->getMessage(),
            ]);
        }

        if ($photoName !== null) {
            $data['photo'] = $photoName;
            $this->deletePhoto($user['photo'] ?? null);
        }

        $this->userModel->update($userId, $data);

        session()->set([
            'userName'  => $data['name'],
            'userEmail' => $data['email'],
            'userPhoto' => $data['photo'] ?? ($user['photo'] ?? null),
        ]);

        return redirect()->to('/admin/profile')->with('success', 'Profil berhasil diperbarui.');
    }

    public function changePassword()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        return view('admin/users/change_password', [
            'userName'  => session()->get('userName'),
            'userEmail' => session()->get('userEmail'),
            'userRole'  => session()->get('userRole'),
        ]);
    }

    public function updatePassword()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        $rules = [
            'old_password'          => 'required',
            'new_password'          => 'required|min_length[6]',
            'confirm_new_password'  => 'required|matches[new_password]',
        ];

        if (! $this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors());
        }

        $userId = (int) session()->get('userId');
        $user   = $this->userModel->find($userId);

        if (! $user) {
            return redirect()->to('/login')->with('error', 'Data user tidak ditemukan. Silakan login ulang.');
        }

        $oldPassword = (string) $this->request->getPost('old_password');

        if (! password_verify($oldPassword, $user['password'])) {
            return redirect()
                ->back()
                ->with('error', 'Password lama tidak sesuai.');
        }

        $newPassword = (string) $this->request->getPost('new_password');

        $this->userModel->update($userId, [
            'password' => password_hash($newPassword, PASSWORD_DEFAULT),
        ]);

        return redirect()
            ->to('/admin/change-password')
            ->with('success', 'Password berhasil diganti.');
    }

    private function isLoggedIn(): bool
    {
        return (bool) session()->get('isLoggedIn') && $this->isUserAccount();
    }

    private function moveTemporaryPhoto(): ?string
    {
        return $this->temporaryImageUpload->moveToPermanent(
            $this->request->getPost('photo_temp'),
            $this->uploadPath,
            ['jpg', 'jpeg', 'png', 'webp']
        );
    }

    private function deletePhoto(?string $photoName): void
    {
        if (! $photoName) {
            return;
        }

        $photoPath = $this->uploadPath . DIRECTORY_SEPARATOR . $photoName;

        if (is_file($photoPath)) {
            unlink($photoPath);
        }
    }

    private function wouldRemoveLastActiveAdministrator(array $user, ?string $newRole, ?string $newStatus): bool
    {
        $isCurrentlyActiveAdministrator = ($user['role'] ?? '') === 'administrator'
            && (int) ($user['is_active'] ?? 0) === 1;

        if (! $isCurrentlyActiveAdministrator) {
            return false;
        }

        $willRemainActiveAdministrator = $newRole === 'administrator' && $newStatus === '1';

        if ($willRemainActiveAdministrator) {
            return false;
        }

        $otherActiveAdministrators = $this->userModel
            ->where('role', 'administrator')
            ->where('is_active', 1)
            ->where('id !=', (int) $user['id'])
            ->countAllResults();

        return $otherActiveAdministrators < 1;
    }

    private function userUsage(int $userId): array
    {
        $db = Database::connect();

        return [
            'pembelian'       => $db->table('pembelian')->where('user_id', $userId)->countAllResults(),
            'penjualan'       => $db->table('penjualan')->where('user_id', $userId)->countAllResults(),
            'service'         => $db->table('service')->where('user_id', $userId)->countAllResults(),
            'stok_penyesuaian'   => $db->table('stok_penyesuaian')
                ->groupStart()
                ->where('user_id', $userId)
                ->orWhere('dibatalkan_oleh', $userId)
                ->groupEnd()
                ->countAllResults(),
        ];
    }

}
