<?php

namespace Modules\Operations\Services;

use App\Models\ActivityLogModel;
use App\Models\EmployeeModel;
use App\Models\InventoryAssetModel;
use App\Models\WorkOrderEquipmentModel;
use App\Models\WorkOrderModel;
use App\Models\WorkOrderStaffModel;

class WorkOrderService
{
    protected WorkOrderModel $orders;
    protected WorkOrderStaffModel $staff;
    protected WorkOrderEquipmentModel $equipment;
    protected EmployeeModel $employees;
    protected InventoryAssetModel $assets;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->orders     = new WorkOrderModel();
        $this->staff      = new WorkOrderStaffModel();
        $this->equipment  = new WorkOrderEquipmentModel();
        $this->employees  = new EmployeeModel();
        $this->assets     = new InventoryAssetModel();
        $this->activity   = new ActivityLogModel();
    }

    public function create(array $input, ?int $actingUserId): array
    {
        $title = trim((string) ($input['title'] ?? ''));
        if ($title === '') {
            return ['success' => false, 'errors' => ['title' => 'Title is required.']];
        }

        $priority = (string) ($input['priority'] ?? 'normal');
        if (! in_array($priority, WorkOrderModel::PRIORITIES, true)) {
            $priority = 'normal';
        }

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

        $data = [
            'number'         => $this->orders->nextNumber(),
            'title'          => $title,
            'description'    => trim((string) ($input['description'] ?? '')) ?: null,
            'priority'       => $priority,
            'status'         => $status,
            'scheduled_date' => $this->nullableDate($input['scheduled_date'] ?? null),
            'due_date'       => $this->nullableDate($input['due_date'] ?? null),
            'location'       => trim((string) ($input['location'] ?? '')) ?: null,
            'notes'          => trim((string) ($input['notes'] ?? '')) ?: null,
            'created_by'     => $actingUserId,
            'updated_by'     => $actingUserId,
        ];

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

        $id = (int) $this->orders->getInsertID();
        $this->activity->log('work_orders', 'created', $actingUserId, "Created work order #{$id} ({$data['number']})", (string) $id);

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

    public function update(int $id, array $input, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'errors' => ['id' => 'Work order not found.']];
        }

        if (! in_array($order['status'], [...WorkOrderModel::OPEN_STATUSES, 'rejected'], true)) {
            return ['success' => false, 'errors' => ['status' => 'Only open or rejected work orders can be edited.']];
        }

        $title = trim((string) ($input['title'] ?? ''));
        if ($title === '') {
            return ['success' => false, 'errors' => ['title' => 'Title is required.']];
        }

        $priority = (string) ($input['priority'] ?? $order['priority']);
        if (! in_array($priority, WorkOrderModel::PRIORITIES, true)) {
            $priority = $order['priority'];
        }

        $data = [
            'title'          => $title,
            'description'    => trim((string) ($input['description'] ?? '')) ?: null,
            'priority'       => $priority,
            'scheduled_date' => $this->nullableDate($input['scheduled_date'] ?? null),
            'due_date'       => $this->nullableDate($input['due_date'] ?? null),
            'location'       => trim((string) ($input['location'] ?? '')) ?: null,
            'notes'          => trim((string) ($input['notes'] ?? '')) ?: null,
            'updated_by'     => $actingUserId,
        ];

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

        $this->activity->log('work_orders', 'updated', $actingUserId, "Updated work order #{$id}", (string) $id);

        return ['success' => true];
    }

    public function updateStatus(int $id, string $status, ?string $notes, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'error' => 'Work order not found.'];
        }

        if (! in_array($status, WorkOrderModel::STATUSES, true)) {
            return ['success' => false, 'error' => 'Invalid status.'];
        }

        if ($order['status'] === 'completed' || $order['status'] === 'cancelled') {
            return ['success' => false, 'error' => 'Completed or cancelled work orders cannot change status here.'];
        }

        if ($status === 'completed') {
            return $this->complete($id, $notes, $actingUserId);
        }

        if ($status === 'cancelled') {
            return $this->cancel($id, $actingUserId);
        }

        $data = [
            'status'     => $status,
            'updated_by' => $actingUserId,
        ];

        if ($status === 'in_progress' && empty($order['started_at'])) {
            $data['started_at'] = date('Y-m-d H:i:s');
        }

        if ($notes !== null && trim($notes) !== '') {
            $data['notes'] = trim($notes);
        }

        $this->orders->update($id, $data);
        $this->activity->log('work_orders', 'status_updated', $actingUserId, "Work order #{$id} status set to {$status}", (string) $id);

        return ['success' => true];
    }

    public function complete(int $id, ?string $completionNotes, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'error' => 'Work order not found.'];
        }

        if (! in_array($order['status'], ['open', 'in_progress', 'on_hold'], true)) {
            return ['success' => false, 'error' => 'Only open, in-progress, or on-hold work orders can be completed.'];
        }

        $this->orders->update($id, [
            'status'           => 'completed',
            'completion_notes' => trim((string) $completionNotes) ?: null,
            'completed_at'     => date('Y-m-d H:i:s'),
            'completed_by'     => $actingUserId,
            'updated_by'       => $actingUserId,
            'started_at'       => $order['started_at'] ?: date('Y-m-d H:i:s'),
        ]);

        $this->activity->log('work_orders', 'completed', $actingUserId, "Completed work order #{$id}", (string) $id);

        return ['success' => true];
    }

    public function cancel(int $id, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'error' => 'Work order not found.'];
        }

        if (! in_array($order['status'], WorkOrderModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'error' => 'Only open work orders can be cancelled.'];
        }

        $this->orders->update($id, [
            'status'     => 'cancelled',
            'updated_by' => $actingUserId,
        ]);
        $this->activity->log('work_orders', 'cancelled', $actingUserId, "Cancelled work order #{$id}", (string) $id);

        return ['success' => true];
    }

    public function delete(int $id, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'error' => 'Work order not found.'];
        }

        if (! in_array($order['status'], ['draft', 'cancelled', 'rejected'], true)) {
            return ['success' => false, 'error' => 'Only draft, cancelled, or rejected work orders can be deleted.'];
        }

        $this->staff->where('work_order_id', $id)->delete();
        $this->equipment->where('work_order_id', $id)->delete();
        $this->orders->delete($id);
        $this->activity->log('work_orders', 'deleted', $actingUserId, "Deleted work order #{$id}", (string) $id);

        return ['success' => true];
    }

    public function submitForApproval(int $id, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'error' => 'Work order not found.'];
        }

        if (! in_array($order['status'], WorkOrderModel::SUBMITTABLE_STATUSES, true)) {
            return ['success' => false, 'error' => 'Only draft, open, or rejected work orders can be submitted for approval.'];
        }

        $this->orders->update($id, [
            'status'           => 'pending_approval',
            'rejection_reason' => null,
            'updated_by'       => $actingUserId,
        ]);
        $this->activity->log('work_orders', 'submitted', $actingUserId, "Submitted work order #{$id} for approval", (string) $id);

        return ['success' => true];
    }

    public function approve(int $id, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'error' => 'Work order not found.'];
        }

        if ($order['status'] !== 'pending_approval') {
            return ['success' => false, 'error' => 'Only pending work orders can be approved.'];
        }

        $this->orders->update($id, [
            'status'           => 'open',
            'rejection_reason' => null,
            'updated_by'       => $actingUserId,
        ]);
        $this->activity->log('work_orders', 'approved', $actingUserId, "Approved work order #{$id}", (string) $id);

        return ['success' => true];
    }

    public function reject(int $id, string $reason, ?int $actingUserId): array
    {
        $order = $this->orders->find($id);
        if (! $order) {
            return ['success' => false, 'error' => 'Work order not found.'];
        }

        if ($order['status'] !== 'pending_approval') {
            return ['success' => false, 'error' => 'Only pending work orders can be rejected.'];
        }

        $this->orders->update($id, [
            'status'           => 'rejected',
            'rejection_reason' => trim($reason) ?: null,
            'updated_by'       => $actingUserId,
        ]);
        $this->activity->log('work_orders', 'rejected', $actingUserId, "Rejected work order #{$id}", (string) $id);

        return ['success' => true];
    }

    public function assignStaff(int $workOrderId, array $input, ?int $actingUserId): array
    {
        $order = $this->orders->find($workOrderId);
        if (! $order) {
            return ['success' => false, 'errors' => ['work_order_id' => 'Work order not found.']];
        }

        if (! in_array($order['status'], WorkOrderModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'errors' => ['status' => 'Staff can only be assigned to open work orders.']];
        }

        $employeeId = (int) ($input['employee_id'] ?? 0);
        if ($employeeId <= 0) {
            return ['success' => false, 'errors' => ['employee_id' => 'Employee is required.']];
        }

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

        if ($this->staff->isAssigned($workOrderId, $employeeId)) {
            return ['success' => false, 'errors' => ['employee_id' => 'This employee is already assigned.']];
        }

        $this->staff->insert([
            'work_order_id' => $workOrderId,
            'employee_id'   => $employeeId,
            'role'          => trim((string) ($input['role'] ?? '')) ?: null,
            'notes'         => trim((string) ($input['notes'] ?? '')) ?: null,
            'assigned_at'   => date('Y-m-d H:i:s'),
            'assigned_by'   => $actingUserId,
        ]);

        $this->activity->log('work_orders', 'staff_assigned', $actingUserId, "Assigned employee #{$employeeId} to work order #{$workOrderId}", (string) $workOrderId);

        return ['success' => true];
    }

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

        $order = $this->orders->find((int) $row['work_order_id']);
        if ($order && ! in_array($order['status'], WorkOrderModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'error' => 'Staff can only be removed from open work orders.'];
        }

        $this->staff->delete($assignmentId);
        $this->activity->log('work_orders', 'staff_removed', $actingUserId, "Removed staff assignment #{$assignmentId}", (string) $row['work_order_id']);

        return ['success' => true];
    }

    public function assignEquipment(int $workOrderId, array $input, ?int $actingUserId): array
    {
        $order = $this->orders->find($workOrderId);
        if (! $order) {
            return ['success' => false, 'errors' => ['work_order_id' => 'Work order not found.']];
        }

        if (! in_array($order['status'], WorkOrderModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'errors' => ['status' => 'Equipment can only be assigned to open work orders.']];
        }

        $assetId = (int) ($input['inventory_asset_id'] ?? 0);
        if ($assetId <= 0) {
            return ['success' => false, 'errors' => ['inventory_asset_id' => 'Equipment is required.']];
        }

        $asset = $this->assets->find($assetId);
        if (! $asset || ($asset['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => ['inventory_asset_id' => 'Select an active inventory asset.']];
        }

        if ($this->equipment->isAssigned($workOrderId, $assetId)) {
            return ['success' => false, 'errors' => ['inventory_asset_id' => 'This equipment is already assigned.']];
        }

        $this->equipment->insert([
            'work_order_id'      => $workOrderId,
            'inventory_asset_id' => $assetId,
            'notes'              => trim((string) ($input['notes'] ?? '')) ?: null,
            'assigned_at'        => date('Y-m-d H:i:s'),
            'assigned_by'        => $actingUserId,
            'returned_at'        => null,
        ]);

        $this->activity->log('work_orders', 'equipment_assigned', $actingUserId, "Assigned asset #{$assetId} to work order #{$workOrderId}", (string) $workOrderId);

        return ['success' => true];
    }

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

        if (! empty($row['returned_at'])) {
            return ['success' => false, 'error' => 'Equipment already returned.'];
        }

        $this->equipment->update($assignmentId, [
            'returned_at' => date('Y-m-d H:i:s'),
        ]);
        $this->activity->log('work_orders', 'equipment_returned', $actingUserId, "Returned equipment assignment #{$assignmentId}", (string) $row['work_order_id']);

        return ['success' => true];
    }

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

        $order = $this->orders->find((int) $row['work_order_id']);
        if ($order && ! in_array($order['status'], WorkOrderModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'error' => 'Equipment can only be removed from open work orders.'];
        }

        $this->equipment->delete($assignmentId);
        $this->activity->log('work_orders', 'equipment_removed', $actingUserId, "Removed equipment assignment #{$assignmentId}", (string) $row['work_order_id']);

        return ['success' => true];
    }

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

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