<?php

namespace App\Controllers;

use App\Models\ActivityLogModel;
use App\Models\PasswordResetCodeModel;
use App\Models\RolePermissionModel;
use App\Models\PayslipModel;
use App\Models\ShiftModel;
use App\Models\StaffDocumentModel;
use App\Models\UserModel;
use Config\Lookups;
use Throwable;

/**
 * Manager-only (enforced by the 'role:manager' route filter, not here —
 * see app/Config/Routes.php). Create/deactivate staff logins, and (see
 * show()) a per-staff profile pulling together data that already exists
 * elsewhere (role permissions, activity log, shift assignments) rather
 * than inventing new tracking for a redesigned mockup — see the build
 * plan's own note on what was deliberately left out (Employee ID/
 * Department/Username/photo/graded permission levels: none of that is
 * real data in this schema).
 */
class UserController extends BaseController
{
    public function index()
    {
        $filters = [
            'q'      => trim((string) $this->request->getGet('q')),
            'role'   => $this->request->getGet('role'),
            'status' => $this->request->getGet('status'),
        ];

        $model = new UserModel();
        $users = $model->filterList($filters)->paginate(10);

        $lastLogins = (new ActivityLogModel())->lastLoginByUser();

        $lookups = new Lookups();

        return view('users/index', [
            'pageTitle'   => 'Staff Users',
            'users'       => $users,
            'pager'       => $model->pager,
            'lastLogins'  => $lastLogins,
            'filters'     => $filters,
            'lookups'     => $lookups,
            'stats'       => [
                'total'    => (new UserModel())->countAllResults(),
                'active'   => (new UserModel())->where('is_active', 1)->countAllResults(),
                'inactive' => (new UserModel())->where('is_active', 0)->countAllResults(),
                'roles'    => count($lookups->staffRoles),
            ],
        ]);
    }

    public function new()
    {
        return view('users/form', [
            'pageTitle' => 'New Staff User',
            'lookups'   => new Lookups(),
        ]);
    }

    public function create()
    {
        $model = new UserModel();

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

        if (strlen((string) $password) < 8) {
            return redirect()->back()->withInput()->with('error', 'Password must be at least 8 characters.');
        }

        $salary = $this->request->getPost('basic_salary');

        $data = [
            'full_name'     => $this->request->getPost('full_name'),
            'email'         => $this->request->getPost('email'),
            'phone'         => $this->request->getPost('phone') ?: null,
            'role'          => $this->request->getPost('role'),
            'password_hash' => password_hash($password, PASSWORD_DEFAULT),
            'is_active'     => 1,
            // Payroll/identification — all optional at creation time too,
            // same as when set later via updateSalary()/
            // updateIdentification() on the Staff Profile page. Letting a
            // manager fill these in up front (e.g. when they're already
            // typing the new hire's details from a physical form) avoids
            // the extra round trip of creating the user, then opening
            // their profile, then filling in the same information again.
            'basic_salary'        => $salary === '' || $salary === null ? null : parse_money($salary),
            'employee_id'         => trim((string) $this->request->getPost('employee_id')) ?: null,
            'ssnit_number'        => trim((string) $this->request->getPost('ssnit_number')) ?: null,
            'ghana_card_number'   => trim((string) $this->request->getPost('ghana_card_number')) ?: null,
            'bank_name'           => trim((string) $this->request->getPost('bank_name')) ?: null,
            'bank_branch'         => trim((string) $this->request->getPost('bank_branch')) ?: null,
            'bank_account_number' => trim((string) $this->request->getPost('bank_account_number')) ?: null,
        ];

        if (! $model->insert($data)) {
            return $this->redirectWithModelErrors($model);
        }

        $this->logActivity('users', 'Created staff user "' . $data['full_name'] . '" (' . $data['role'] . ').');

        return redirect()->to('users')->with('success', 'Staff user created.');
    }

    /**
     * Staff Profile — Personal/Account Information are the real columns
     * on `users` only (see class doc comment for what's deliberately not
     * here). Permissions/Activity Log/Shift Assignments tabs each reuse
     * an existing subsystem's own data rather than duplicating it.
     */
    public function show(int $id)
    {
        $user = (new UserModel())->find($id);

        if ($user === null) {
            return redirect()->to('users')->with('error', 'User not found.');
        }

        $lookups = new Lookups();

        $lastLogins = (new ActivityLogModel())->lastLoginByUser();
        $activity   = (new ActivityLogModel())->forUser($id, 8);
        $shifts     = (new ShiftModel())->forUser($id, date('Y-m-d'));
        $permissions = (new RolePermissionModel())->forRole($user->role);
        $documents   = (new StaffDocumentModel())->forUser($id);
        $payslips    = (new PayslipModel())->forUser($id);

        return view('users/show', [
            'pageTitle'   => $user->full_name,
            'user'        => $user,
            'lookups'     => $lookups,
            'lastLogin'   => $lastLogins[$id] ?? null,
            'activity'    => $activity,
            'shifts'      => $shifts,
            'permissions' => $permissions,
            'documents'   => $documents,
            'payslips'    => $payslips,
        ]);
    }

    public function toggleActive(int $id)
    {
        $model = new UserModel();
        $user  = $model->find($id);

        if ($user === null) {
            return redirect()->back()->with('error', 'User not found.');
        }

        if ($id === $this->currentUserId()) {
            return redirect()->back()->with('error', 'You cannot deactivate your own account.');
        }

        $newStatus = $user->is_active ? 0 : 1;
        $model->update($id, ['is_active' => $newStatus]);

        $this->logActivity('users', ($newStatus ? 'Reactivated' : 'Deactivated') . " staff user \"{$user->full_name}\".");

        return redirect()->back()->with('success', 'User ' . ($newStatus ? 'reactivated' : 'deactivated') . '.');
    }

    /**
     * Manager-triggered password reset — reuses the exact same SMS-code
     * self-service flow ForgotPasswordController already gives staff for
     * their own accounts (PasswordResetCodeModel), just initiated by a
     * manager on someone else's behalf rather than by the account owner.
     * No new password mechanism, and no plaintext password is ever
     * generated or shown to the manager — the code only lets the staff
     * member set their own new one via the existing forgot-password/verify
     * flow.
     */
    public function resetPassword(int $id)
    {
        $user = (new UserModel())->find($id);

        if ($user === null) {
            return redirect()->back()->with('error', 'User not found.');
        }

        if (empty($user->phone)) {
            return redirect()->back()->with('error', 'This staff member has no phone number on file — add one before resetting their password.');
        }

        $code = (new PasswordResetCodeModel())->generateFor($id);

        try {
            service('hubtelService')->sendSms(
                $user->phone,
                "Your MaxYields PMS password reset code is {$code}. It expires in 10 minutes.",
            );
        } catch (Throwable $e) {
            log_message('error', 'Manager-triggered password reset SMS failed: ' . $e->getMessage());
        }

        $this->logActivity('users', "Sent a password reset code to \"{$user->full_name}\".");

        return redirect()->back()->with('success', 'A reset code was texted to ' . $user->full_name . '.');
    }

    /**
     * Same handle_image_upload()/UploadController::serve() pattern as
     * every other uploaded image in this app (tenant logo, menu photos,
     * room type photos) — a staff headshot isn't more sensitive than
     * those categories, so it's public-served the same way, not the
     * authenticated path documents use (see uploadDocument()).
     */
    public function uploadPhoto(int $id)
    {
        $model = new UserModel();
        $user  = $model->find($id);

        if ($user === null) {
            return redirect()->back()->with('error', 'User not found.');
        }

        $upload = handle_image_upload($this->request->getFile('photo'), 'staff-photos');

        if ($upload['error']) {
            return redirect()->back()->with('error', $upload['error']);
        }

        if ($upload['path'] === null) {
            return redirect()->back()->with('error', 'Choose a photo to upload.');
        }

        $model->update($id, ['photo_path' => $upload['path']]);
        $this->logActivity('users', "Updated the profile photo for \"{$user->full_name}\".");

        return redirect()->back()->with('success', 'Photo updated.');
    }

    /**
     * handle_document_upload(), not handle_image_upload() — a broader
     * file-type allowlist (PDF/DOC/DOCX in addition to images) and a
     * larger size cap, since employment documents aren't decorative
     * photos. Stored the same writable/uploads/ way, but downloaded only
     * through downloadDocument() below (manager-authenticated), never
     * the public UploadController::serve() route staff photos use.
     */
    public function uploadDocument(int $id)
    {
        $user = (new UserModel())->find($id);

        if ($user === null) {
            return redirect()->back()->with('error', 'User not found.');
        }

        $upload = handle_document_upload($this->request->getFile('document'), 'staff-documents');

        if ($upload['error']) {
            return redirect()->back()->with('error', $upload['error']);
        }

        if ($upload['path'] === null) {
            return redirect()->back()->with('error', 'Choose a file to upload.');
        }

        $documents = new StaffDocumentModel();
        $data = [
            'user_id'           => $id,
            'document_type'     => $this->request->getPost('document_type'),
            'file_path'         => $upload['path'],
            'original_filename' => $upload['originalName'],
            'uploaded_by'       => $this->currentUserId(),
        ];

        if (! $documents->insert($data)) {
            return $this->redirectWithModelErrors($documents);
        }

        $this->logActivity('users', 'Uploaded a document for "' . $user->full_name . '" (' . $data['document_type'] . ').');

        return redirect()->back()->with('success', 'Document uploaded.');
    }

    /** Forced download, manager-only — the whole reason this isn't served through the public UploadController route. */
    public function downloadDocument(int $id, int $documentId)
    {
        $document = (new StaffDocumentModel())->where('user_id', $id)->find($documentId);

        if ($document === null) {
            return redirect()->back()->with('error', 'Document not found.');
        }

        $path = WRITEPATH . 'uploads/' . $document->file_path;

        if (! is_file($path)) {
            return redirect()->back()->with('error', 'That file is missing from storage.');
        }

        $this->logActivity('users', 'Downloaded a document (' . $document->original_filename . ').');

        return $this->response
            ->setHeader('Content-Type', guess_mime_type($path))
            ->setHeader('Content-Disposition', 'attachment; filename="' . $document->original_filename . '"')
            ->setBody(file_get_contents($path));
    }

    public function deleteDocument(int $id, int $documentId)
    {
        $documents = new StaffDocumentModel();
        $document  = $documents->where('user_id', $id)->find($documentId);

        if ($document === null) {
            return redirect()->back()->with('error', 'Document not found.');
        }

        $path = WRITEPATH . 'uploads/' . $document->file_path;

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

        $documents->delete($documentId);
        $this->logActivity('users', 'Deleted a document (' . $document->original_filename . ').');

        return redirect()->back()->with('success', 'Document deleted.');
    }

    /**
     * Sets a staff member's current standard monthly salary — only used
     * to pre-fill new payslips when a payroll run is generated
     * (PayrollController::create()); editing this never touches past
     * payslips, which freeze their own basic_salary at generation time.
     */
    public function updateSalary(int $id)
    {
        $model = new UserModel();
        $user  = $model->find($id);

        if ($user === null) {
            return redirect()->back()->with('error', 'User not found.');
        }

        $salary = $this->request->getPost('basic_salary');

        $model->update($id, ['basic_salary' => $salary === '' ? null : parse_money($salary)]);
        $this->logActivity('users', "Updated the basic salary for \"{$user->full_name}\".");

        return redirect()->back()->with('success', 'Basic salary updated.');
    }

    /**
     * Employee ID/SSNIT/Ghana Card/bank reference — all optional, shown
     * on the payslip PDF (PayrollController::payslipPdf()). Record-
     * keeping only, same as the rest of payroll: this app prints a bank
     * account number, it never pays into one.
     */
    public function updateIdentification(int $id)
    {
        $model = new UserModel();
        $user  = $model->find($id);

        if ($user === null) {
            return redirect()->back()->with('error', 'User not found.');
        }

        $data = [
            'employee_id'         => trim((string) $this->request->getPost('employee_id')) ?: null,
            'ssnit_number'        => trim((string) $this->request->getPost('ssnit_number')) ?: null,
            'ghana_card_number'   => trim((string) $this->request->getPost('ghana_card_number')) ?: null,
            'bank_name'           => trim((string) $this->request->getPost('bank_name')) ?: null,
            'bank_branch'         => trim((string) $this->request->getPost('bank_branch')) ?: null,
            'bank_account_number' => trim((string) $this->request->getPost('bank_account_number')) ?: null,
        ];

        // 'id' => $id: same fix as ProfileController::update() — without
        // it, the tenant_unique[...,id,{id}] rules on employee_id/
        // ssnit_number/ghana_card_number never actually exclude this
        // user's own current row, so re-saving an unchanged value (or
        // just changing one of the other fields while these stay as they
        // were) would incorrectly fail as a false "already taken"
        // collision against yourself. Latent since this field set was
        // added — nothing had exercised a same-value re-save until this
        // was traced from a live failure in the new Profile page.
        if (! $model->update($id, $data + ['id' => $id])) {
            return $this->redirectWithModelErrors($model);
        }

        $this->logActivity('users', "Updated identification/banking details for \"{$user->full_name}\".");

        return redirect()->back()->with('success', 'Identification details updated.');
    }
}
