<?php

namespace Modules\Operations\Services;

use App\Models\ActivityLogModel;
use App\Models\EmployeeModel;
use App\Models\InventoryAssetModel;
use App\Models\OpsEquipmentAllocationModel;
use App\Models\OpsMaterialUsageModel;
use App\Models\OpsStaffAllocationModel;
use App\Models\ProductModel;
use App\Models\WorkOrderModel;
use Config\Database;

class OperationsResourcesService
{
    protected OpsStaffAllocationModel $staff;
    protected OpsEquipmentAllocationModel $equipment;
    protected OpsMaterialUsageModel $materials;
    protected EmployeeModel $employees;
    protected InventoryAssetModel $assets;
    protected ProductModel $products;
    protected WorkOrderModel $workOrders;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->staff      = new OpsStaffAllocationModel();
        $this->equipment  = new OpsEquipmentAllocationModel();
        $this->materials  = new OpsMaterialUsageModel();
        $this->employees  = new EmployeeModel();
        $this->assets     = new InventoryAssetModel();
        $this->products   = new ProductModel();
        $this->workOrders = new WorkOrderModel();
        $this->activity   = new ActivityLogModel();
    }

    // -------------------------------------------------------------------------
    // Staff allocations
    // -------------------------------------------------------------------------

    public function createStaff(array $input, ?int $actingUserId): array
    {
        $employeeId = (int) ($input['employee_id'] ?? 0);
        $employee   = $employeeId > 0 ? $this->employees->find($employeeId) : null;
        if (! $employee || ($employee['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['employee_id' => 'Select an active employee.']];
        }

        $context = $this->resolveContext($input);
        if (! $context['success']) {
            return $context;
        }

        $status = (string) ($input['status'] ?? 'planned');
        if (! in_array($status, ['planned', 'active'], true)) {
            $status = 'planned';
        }

        $data = [
            'number'           => $this->staff->nextNumber(),
            'employee_id'      => $employeeId,
            'work_order_id'    => $context['work_order_id'],
            'service_visit_id' => $context['service_visit_id'],
            'role'             => trim((string) ($input['role'] ?? '')) ?: null,
            'status'           => $status,
            'from_date'        => $this->nullableDate($input['from_date'] ?? null),
            'to_date'          => $this->nullableDate($input['to_date'] ?? null),
            'notes'            => trim((string) ($input['notes'] ?? '')) ?: null,
            'assigned_at'      => $status === 'active' ? date('Y-m-d H:i:s') : null,
            'assigned_by'      => $status === 'active' ? $actingUserId : null,
            'created_by'       => $actingUserId,
            'updated_by'       => $actingUserId,
        ];

        if (! $this->staff->insert($data, true)) {
            return ['success' => false, 'errors' => $this->staff->errors()];
        }

        $id = (int) $this->staff->getInsertID();
        $this->activity->log('operations_resources', 'staff_created', $actingUserId, "Created staff allocation #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function updateStaff(int $id, array $input, ?int $actingUserId): array
    {
        $row = $this->staff->find($id);
        if (! $row) {
            return ['success' => false, 'errors' => ['id' => 'Staff allocation not found.']];
        }

        if (! in_array($row['status'], OpsStaffAllocationModel::EDITABLE_STATUSES, true)) {
            return ['success' => false, 'errors' => ['status' => 'Only planned allocations can be edited.']];
        }

        $employeeId = (int) ($input['employee_id'] ?? 0);
        $employee   = $employeeId > 0 ? $this->employees->find($employeeId) : null;
        if (! $employee || ($employee['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['employee_id' => 'Select an active employee.']];
        }

        $context = $this->resolveContext($input);
        if (! $context['success']) {
            return $context;
        }

        $data = [
            'employee_id'      => $employeeId,
            'work_order_id'    => $context['work_order_id'],
            'service_visit_id' => $context['service_visit_id'],
            'role'             => trim((string) ($input['role'] ?? '')) ?: null,
            'from_date'        => $this->nullableDate($input['from_date'] ?? null),
            'to_date'          => $this->nullableDate($input['to_date'] ?? null),
            'notes'            => trim((string) ($input['notes'] ?? '')) ?: null,
            'updated_by'       => $actingUserId,
        ];

        if (! $this->staff->update($id, $data)) {
            return ['success' => false, 'errors' => $this->staff->errors()];
        }

        $this->activity->log('operations_resources', 'staff_updated', $actingUserId, "Updated staff allocation #{$id}", (string) $id);

        return ['success' => true];
    }

    public function activateStaff(int $id, ?int $actingUserId): array
    {
        $row = $this->staff->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Staff allocation not found.'];
        }

        if ($row['status'] !== 'planned') {
            return ['success' => false, 'error' => 'Only planned allocations can be activated.'];
        }

        $this->staff->update($id, [
            'status'      => 'active',
            'assigned_at' => date('Y-m-d H:i:s'),
            'assigned_by' => $actingUserId,
            'updated_by'  => $actingUserId,
        ]);
        $this->activity->log('operations_resources', 'staff_activated', $actingUserId, "Activated staff allocation #{$id}", (string) $id);

        return ['success' => true];
    }

    public function releaseStaff(int $id, ?int $actingUserId): array
    {
        $row = $this->staff->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Staff allocation not found.'];
        }

        if (! in_array($row['status'], ['planned', 'active'], true)) {
            return ['success' => false, 'error' => 'Only planned or active allocations can be released.'];
        }

        $this->staff->update($id, [
            'status'     => 'released',
            'updated_by' => $actingUserId,
        ]);
        $this->activity->log('operations_resources', 'staff_released', $actingUserId, "Released staff allocation #{$id}", (string) $id);

        return ['success' => true];
    }

    public function deleteStaff(int $id, ?int $actingUserId): array
    {
        $row = $this->staff->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Staff allocation not found.'];
        }

        if (! in_array($row['status'], ['planned', 'released'], true)) {
            return ['success' => false, 'error' => 'Only planned or released allocations can be deleted.'];
        }

        $this->staff->delete($id);
        $this->activity->log('operations_resources', 'staff_deleted', $actingUserId, "Deleted staff allocation #{$id}", (string) $id);

        return ['success' => true];
    }

    // -------------------------------------------------------------------------
    // Equipment allocations
    // -------------------------------------------------------------------------

    public function createEquipment(array $input, ?int $actingUserId): array
    {
        $assetId = (int) ($input['inventory_asset_id'] ?? 0);
        $asset   = $assetId > 0 ? $this->assets->find($assetId) : null;
        if (! $asset || ($asset['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['inventory_asset_id' => 'Select an active inventory asset.']];
        }

        $context = $this->resolveContext($input);
        if (! $context['success']) {
            return $context;
        }

        $data = [
            'number'             => $this->equipment->nextNumber(),
            'inventory_asset_id' => $assetId,
            'work_order_id'      => $context['work_order_id'],
            'service_visit_id'   => $context['service_visit_id'],
            'status'             => 'assigned',
            'notes'              => trim((string) ($input['notes'] ?? '')) ?: null,
            'assigned_at'        => date('Y-m-d H:i:s'),
            'assigned_by'        => $actingUserId,
            'created_by'         => $actingUserId,
            'updated_by'         => $actingUserId,
        ];

        if (! $this->equipment->insert($data, true)) {
            return ['success' => false, 'errors' => $this->equipment->errors()];
        }

        $id = (int) $this->equipment->getInsertID();
        $this->activity->log('operations_resources', 'equipment_created', $actingUserId, "Created equipment allocation #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function updateEquipment(int $id, array $input, ?int $actingUserId): array
    {
        $row = $this->equipment->find($id);
        if (! $row) {
            return ['success' => false, 'errors' => ['id' => 'Equipment allocation not found.']];
        }

        if (! in_array($row['status'], OpsEquipmentAllocationModel::EDITABLE_STATUSES, true)) {
            return ['success' => false, 'errors' => ['status' => 'Only assigned allocations can be edited.']];
        }

        $assetId = (int) ($input['inventory_asset_id'] ?? 0);
        $asset   = $assetId > 0 ? $this->assets->find($assetId) : null;
        $sameAsset = $asset && (int) $row['inventory_asset_id'] === $assetId;
        if (! $asset || (! $sameAsset && ($asset['status'] ?? '') !== 'active') || ($sameAsset && in_array($asset['status'] ?? '', ['disposed', 'lost'], true))) {
            return ['success' => false, 'errors' => ['inventory_asset_id' => 'Select an active inventory asset.']];
        }

        $context = $this->resolveContext($input);
        if (! $context['success']) {
            return $context;
        }

        $data = [
            'inventory_asset_id' => $assetId,
            'work_order_id'      => $context['work_order_id'],
            'service_visit_id'   => $context['service_visit_id'],
            'notes'              => trim((string) ($input['notes'] ?? '')) ?: null,
            'updated_by'         => $actingUserId,
        ];

        if (! $this->equipment->update($id, $data)) {
            return ['success' => false, 'errors' => $this->equipment->errors()];
        }

        $this->activity->log('operations_resources', 'equipment_updated', $actingUserId, "Updated equipment allocation #{$id}", (string) $id);

        return ['success' => true];
    }

    public function returnEquipment(int $id, ?int $actingUserId): array
    {
        $row = $this->equipment->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Equipment allocation not found.'];
        }

        if ($row['status'] !== 'assigned') {
            return ['success' => false, 'error' => 'Only assigned equipment can be returned.'];
        }

        $this->equipment->update($id, [
            'status'      => 'returned',
            'returned_at' => date('Y-m-d H:i:s'),
            'updated_by'  => $actingUserId,
        ]);
        $this->activity->log('operations_resources', 'equipment_returned', $actingUserId, "Returned equipment allocation #{$id}", (string) $id);

        return ['success' => true];
    }

    public function markLost(int $id, ?int $actingUserId): array
    {
        $row = $this->equipment->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Equipment allocation not found.'];
        }

        if ($row['status'] !== 'assigned') {
            return ['success' => false, 'error' => 'Only assigned equipment can be marked lost.'];
        }

        $this->equipment->update($id, [
            'status'     => 'lost',
            'updated_by' => $actingUserId,
        ]);
        $this->activity->log('operations_resources', 'equipment_lost', $actingUserId, "Marked equipment allocation #{$id} as lost", (string) $id);

        return ['success' => true];
    }

    public function deleteEquipment(int $id, ?int $actingUserId): array
    {
        $row = $this->equipment->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Equipment allocation not found.'];
        }

        if (! in_array($row['status'], ['assigned', 'returned'], true)) {
            return ['success' => false, 'error' => 'Only assigned or returned allocations can be deleted.'];
        }

        $this->equipment->delete($id);
        $this->activity->log('operations_resources', 'equipment_deleted', $actingUserId, "Deleted equipment allocation #{$id}", (string) $id);

        return ['success' => true];
    }

    // -------------------------------------------------------------------------
    // Material usage
    // -------------------------------------------------------------------------

    public function createMaterial(array $input, ?int $actingUserId): array
    {
        $productId = (int) ($input['product_id'] ?? 0);
        $product   = $productId > 0 ? $this->products->find($productId) : null;
        if (! $product || ($product['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['product_id' => 'Select an active product.']];
        }

        $quantity = (float) ($input['quantity'] ?? 0);
        if ($quantity <= 0) {
            return ['success' => false, 'errors' => ['quantity' => 'Quantity must be greater than zero.']];
        }

        $usedById = (int) ($input['used_by_employee_id'] ?? 0);
        if ($usedById > 0) {
            $usedBy = $this->employees->find($usedById);
            if (! $usedBy || ($usedBy['status'] ?? '') !== 'active') {
                return ['success' => false, 'errors' => ['used_by_employee_id' => 'Select an active employee.']];
            }
        } else {
            $usedById = null;
        }

        $context = $this->resolveContext($input);
        if (! $context['success']) {
            return $context;
        }

        $usedAt = trim((string) ($input['used_at'] ?? ''));
        if ($usedAt === '') {
            $usedAt = date('Y-m-d H:i:s');
        } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $usedAt)) {
            $usedAt .= ' 00:00:00';
        }

        $data = [
            'number'              => $this->materials->nextNumber(),
            'product_id'          => $productId,
            'work_order_id'       => $context['work_order_id'],
            'service_visit_id'    => $context['service_visit_id'],
            'quantity'            => $quantity,
            'unit_label'          => trim((string) ($input['unit_label'] ?? '')) ?: null,
            'used_at'             => $usedAt,
            'used_by_employee_id' => $usedById,
            'notes'               => trim((string) ($input['notes'] ?? '')) ?: null,
            'status'              => 'recorded',
            'recorded_by'         => $actingUserId,
            'created_by'          => $actingUserId,
            'updated_by'          => $actingUserId,
        ];

        if (! $this->materials->insert($data, true)) {
            return ['success' => false, 'errors' => $this->materials->errors()];
        }

        $id = (int) $this->materials->getInsertID();
        $this->activity->log('operations_resources', 'material_created', $actingUserId, "Created material usage #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function updateMaterial(int $id, array $input, ?int $actingUserId): array
    {
        $row = $this->materials->find($id);
        if (! $row) {
            return ['success' => false, 'errors' => ['id' => 'Material usage not found.']];
        }

        if (! in_array($row['status'], OpsMaterialUsageModel::EDITABLE_STATUSES, true)) {
            return ['success' => false, 'errors' => ['status' => 'Only recorded usage can be edited.']];
        }

        $productId = (int) ($input['product_id'] ?? 0);
        $product   = $productId > 0 ? $this->products->find($productId) : null;
        if (! $product || ($product['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['product_id' => 'Select an active product.']];
        }

        $quantity = (float) ($input['quantity'] ?? 0);
        if ($quantity <= 0) {
            return ['success' => false, 'errors' => ['quantity' => 'Quantity must be greater than zero.']];
        }

        $usedById = (int) ($input['used_by_employee_id'] ?? 0);
        if ($usedById > 0) {
            $usedBy = $this->employees->find($usedById);
            if (! $usedBy || ($usedBy['status'] ?? '') !== 'active') {
                return ['success' => false, 'errors' => ['used_by_employee_id' => 'Select an active employee.']];
            }
        } else {
            $usedById = null;
        }

        $context = $this->resolveContext($input);
        if (! $context['success']) {
            return $context;
        }

        $usedAt = trim((string) ($input['used_at'] ?? ''));
        if ($usedAt === '') {
            $usedAt = $row['used_at'] ?? date('Y-m-d H:i:s');
        } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $usedAt)) {
            $usedAt .= ' 00:00:00';
        }

        $data = [
            'product_id'          => $productId,
            'work_order_id'       => $context['work_order_id'],
            'service_visit_id'    => $context['service_visit_id'],
            'quantity'            => $quantity,
            'unit_label'          => trim((string) ($input['unit_label'] ?? '')) ?: null,
            'used_at'             => $usedAt,
            'used_by_employee_id' => $usedById,
            'notes'               => trim((string) ($input['notes'] ?? '')) ?: null,
            'updated_by'          => $actingUserId,
        ];

        if (! $this->materials->update($id, $data)) {
            return ['success' => false, 'errors' => $this->materials->errors()];
        }

        $this->activity->log('operations_resources', 'material_updated', $actingUserId, "Updated material usage #{$id}", (string) $id);

        return ['success' => true];
    }

    public function voidUsage(int $id, ?int $actingUserId): array
    {
        $row = $this->materials->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Material usage not found.'];
        }

        if ($row['status'] !== 'recorded') {
            return ['success' => false, 'error' => 'Only recorded usage can be voided.'];
        }

        $this->materials->update($id, [
            'status'     => 'voided',
            'updated_by' => $actingUserId,
        ]);
        $this->activity->log('operations_resources', 'material_voided', $actingUserId, "Voided material usage #{$id}", (string) $id);

        return ['success' => true];
    }

    public function deleteMaterial(int $id, ?int $actingUserId): array
    {
        $row = $this->materials->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Material usage not found.'];
        }

        if (! in_array($row['status'], ['recorded', 'voided'], true)) {
            return ['success' => false, 'error' => 'Material usage cannot be deleted.'];
        }

        $this->materials->delete($id);
        $this->activity->log('operations_resources', 'material_deleted', $actingUserId, "Deleted material usage #{$id}", (string) $id);

        return ['success' => true];
    }

    // -------------------------------------------------------------------------
    // Shared helpers
    // -------------------------------------------------------------------------

    /**
     * Require at least one context when possible; allow neither for general allocation.
     *
     * @return array{success: bool, work_order_id?: ?int, service_visit_id?: ?int, errors?: array}
     */
    protected function resolveContext(array $input): array
    {
        $workOrderId = (int) ($input['work_order_id'] ?? 0);
        $visitId     = (int) ($input['service_visit_id'] ?? 0);

        if ($workOrderId > 0) {
            $wo = $this->workOrders->find($workOrderId);
            if (! $wo) {
                return ['success' => false, 'errors' => ['work_order_id' => 'Work order not found.']];
            }
        } else {
            $workOrderId = null;
        }

        if ($visitId > 0) {
            if (! $this->serviceVisitExists($visitId)) {
                return ['success' => false, 'errors' => ['service_visit_id' => 'Service visit not found.']];
            }
        } else {
            $visitId = null;
        }

        return [
            'success'          => true,
            'work_order_id'    => $workOrderId,
            'service_visit_id' => $visitId,
        ];
    }

    protected function serviceVisitExists(int $id): bool
    {
        $db = Database::connect();
        if (! $db->tableExists('fs_service_visits')) {
            return false;
        }

        return $db->table('fs_service_visits')->where('id', $id)->countAllResults() > 0;
    }

    /**
     * @return list<array>
     */
    public function openServiceVisits(): array
    {
        if (class_exists(\App\Models\FsServiceVisitModel::class)) {
            $model = new \App\Models\FsServiceVisitModel();
            if (method_exists($model, 'getOpenForSelect')) {
                return $model->getOpenForSelect();
            }
            if (method_exists($model, 'getOpenForAssign')) {
                return $model->getOpenForAssign();
            }
            if (defined(\App\Models\FsServiceVisitModel::class . '::OPEN_STATUSES')) {
                return $model->whereIn('status', \App\Models\FsServiceVisitModel::OPEN_STATUSES)
                    ->orderBy('scheduled_date', 'ASC')
                    ->orderBy('id', 'DESC')
                    ->findAll();
            }
        }

        $db = Database::connect();
        if (! $db->tableExists('fs_service_visits')) {
            return [];
        }

        return $db->table('fs_service_visits')
            ->whereIn('status', ['draft', 'scheduled', 'en_route', 'on_site'])
            ->orderBy('scheduled_date', 'ASC')
            ->orderBy('id', 'DESC')
            ->get()
            ->getResultArray();
    }

    protected function nullableDate(mixed $value): ?string
    {
        $value = trim((string) $value);

        return $value !== '' ? $value : null;
    }
}
