<?php

namespace Modules\Sale\Services;

use App\Models\ActivityLogModel;
use App\Models\CompanyModel;
use App\Models\DeliveryLineModel;
use App\Models\DeliveryModel;
use App\Models\SalesOrderLineModel;
use App\Models\SalesOrderModel;

class DeliveryService
{
    protected DeliveryModel $deliveries;
    protected DeliveryLineModel $lines;
    protected CompanyModel $companies;
    protected SalesOrderModel $orders;
    protected SalesOrderLineModel $orderLines;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->deliveries = new DeliveryModel();
        $this->lines      = new DeliveryLineModel();
        $this->companies  = new CompanyModel();
        $this->orders     = new SalesOrderModel();
        $this->orderLines = new SalesOrderLineModel();
        $this->activity   = new ActivityLogModel();
    }

    public function create(array $input, ?int $actingUserId): array
    {
        $companyId = (int) ($input['company_id'] ?? 0);
        if (! $this->companies->find($companyId)) {
            return ['success' => false, 'errors' => ['company_id' => 'Company not found.']];
        }

        $data = $this->mapInput($input);
        $data['number']     = $this->deliveries->nextNumber();
        $data['status']     = in_array($input['status'] ?? 'draft', ['draft', 'ready'], true)
            ? ($input['status'] ?? 'draft')
            : 'draft';
        $data['created_by'] = $actingUserId;
        $data['updated_by'] = $actingUserId;

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

        $id = (int) $this->deliveries->getInsertID();
        $this->lines->replaceForDelivery($id, $input['lines'] ?? []);
        $this->activity->log('deliveries', 'created', $actingUserId, "Created delivery #{$id} ({$data['number']})", (string) $id);

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

    public function createFromOrder(int $orderId, ?int $actingUserId): array
    {
        $order = $this->orders->find($orderId);
        if (! $order) {
            return ['success' => false, 'error' => 'Sales order not found.'];
        }
        if (! in_array($order['status'], ['approved', 'confirmed', 'in_progress'], true)) {
            return ['success' => false, 'error' => 'Order must be approved before creating a delivery note.'];
        }

        $oLines = $this->orderLines->getForOrder($orderId);
        $lines = [];
        foreach ($oLines as $line) {
            $lines[] = [
                'item_code' => $line['item_code'],
                'item_name' => $line['item_name'],
                'quantity'  => $line['quantity'],
                'unit'      => $line['unit'],
            ];
        }

        return $this->create([
            'sales_order_id' => $orderId,
            'company_id'     => $order['company_id'],
            'delivery_date'  => date('Y-m-d'),
            'status'         => 'draft',
            'notes'          => $order['notes'],
            'lines'          => $lines,
        ], $actingUserId);
    }

    public function update(int $id, array $input, ?int $actingUserId): array
    {
        $delivery = $this->deliveries->find($id);
        if (! $delivery) {
            return ['success' => false, 'errors' => ['id' => 'Delivery not found.']];
        }
        if (! in_array($delivery['status'], ['draft', 'ready'], true)) {
            return ['success' => false, 'errors' => ['status' => 'Only draft or ready deliveries can be edited.']];
        }

        $companyId = (int) ($input['company_id'] ?? 0);
        if (! $this->companies->find($companyId)) {
            return ['success' => false, 'errors' => ['company_id' => 'Company not found.']];
        }

        $data = $this->mapInput($input);
        $data['status']     = in_array($input['status'] ?? $delivery['status'], ['draft', 'ready'], true)
            ? ($input['status'] ?? $delivery['status'])
            : $delivery['status'];
        $data['updated_by'] = $actingUserId;

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

        $this->lines->replaceForDelivery($id, $input['lines'] ?? []);
        $this->activity->log('deliveries', 'updated', $actingUserId, "Updated delivery #{$id}", (string) $id);

        return ['success' => true];
    }

    public function markReady(int $id, ?int $actingUserId): array
    {
        $delivery = $this->deliveries->find($id);
        if (! $delivery) {
            return ['success' => false, 'error' => 'Delivery not found.'];
        }
        if ($delivery['status'] !== 'draft') {
            return ['success' => false, 'error' => 'Only draft deliveries can be marked ready.'];
        }

        $this->deliveries->update($id, ['status' => 'ready', 'updated_by' => $actingUserId]);
        $this->activity->log('deliveries', 'ready', $actingUserId, "Marked delivery #{$id} as ready", (string) $id);

        return ['success' => true];
    }

    public function dispatch(int $id, array $input, ?int $actingUserId): array
    {
        $delivery = $this->deliveries->find($id);
        if (! $delivery) {
            return ['success' => false, 'error' => 'Delivery not found.'];
        }
        if (! in_array($delivery['status'], ['ready', 'draft'], true)) {
            return ['success' => false, 'error' => 'Delivery cannot be dispatched from its current status.'];
        }

        $this->deliveries->update($id, [
            'status'          => 'dispatched',
            'carrier'         => trim($input['carrier'] ?? '') ?: $delivery['carrier'],
            'tracking_number' => trim($input['tracking_number'] ?? '') ?: $delivery['tracking_number'],
            'dispatched_at'   => date('Y-m-d H:i:s'),
            'updated_by'      => $actingUserId,
        ]);
        $this->activity->log('deliveries', 'dispatched', $actingUserId, "Dispatched delivery #{$id}", (string) $id);

        return ['success' => true];
    }

    public function updateTracking(int $id, string $status, array $input, ?int $actingUserId): array
    {
        $delivery = $this->deliveries->find($id);
        if (! $delivery) {
            return ['success' => false, 'error' => 'Delivery not found.'];
        }

        $allowed = ['dispatched', 'in_transit', 'delivered', 'cancelled'];
        if (! in_array($status, $allowed, true)) {
            return ['success' => false, 'error' => 'Invalid tracking status.'];
        }
        if (! in_array($delivery['status'], ['dispatched', 'in_transit', 'delivered'], true)
            && $delivery['status'] !== 'dispatched') {
            if (! in_array($delivery['status'], ['dispatched', 'in_transit'], true)) {
                return ['success' => false, 'error' => 'Delivery must be dispatched before tracking updates.'];
            }
        }
        if (! in_array($delivery['status'], ['dispatched', 'in_transit', 'delivered'], true)) {
            return ['success' => false, 'error' => 'Delivery must be dispatched before tracking updates.'];
        }

        $update = [
            'status'          => $status,
            'carrier'         => trim($input['carrier'] ?? '') ?: $delivery['carrier'],
            'tracking_number' => trim($input['tracking_number'] ?? '') ?: $delivery['tracking_number'],
            'notes'           => array_key_exists('notes', $input) ? ($input['notes'] ?: null) : $delivery['notes'],
            'updated_by'      => $actingUserId,
        ];
        if ($status === 'delivered') {
            $update['delivered_at'] = date('Y-m-d H:i:s');
        }

        $this->deliveries->update($id, $update);
        $this->activity->log('deliveries', 'tracking_updated', $actingUserId, "Updated delivery #{$id} tracking to {$status}", (string) $id);

        return ['success' => true];
    }

    public function delete(int $id, ?int $actingUserId): array
    {
        $delivery = $this->deliveries->find($id);
        if (! $delivery) {
            return ['success' => false, 'error' => 'Delivery not found.'];
        }
        if (! in_array($delivery['status'], ['draft', 'cancelled'], true)) {
            return ['success' => false, 'error' => 'Only draft or cancelled deliveries can be deleted.'];
        }

        $this->lines->where('delivery_id', $id)->delete();
        $this->deliveries->delete($id);
        $this->activity->log('deliveries', 'deleted', $actingUserId, "Deleted delivery #{$id}", (string) $id);

        return ['success' => true];
    }

    protected function mapInput(array $input): array
    {
        return [
            'sales_order_id'   => ! empty($input['sales_order_id']) ? (int) $input['sales_order_id'] : null,
            'company_id'       => (int) $input['company_id'],
            'delivery_date'    => $input['delivery_date'] ?? date('Y-m-d'),
            'carrier'          => trim($input['carrier'] ?? '') ?: null,
            'tracking_number'  => trim($input['tracking_number'] ?? '') ?: null,
            'shipping_address' => $input['shipping_address'] ?: null,
            'notes'            => $input['notes'] ?: null,
        ];
    }
}
