<?php

namespace Modules\Vrc\Services\RepairOrder;

use App\Utils\Util;
use Illuminate\Support\Facades\DB;
use Modules\Vrc\Entities\VrcAppointment;
use Modules\Vrc\Entities\VrcRepairOrder;
use Modules\Vrc\Entities\VrcRepairOrderStatusHistory;
use Modules\Vrc\Entities\VrcVehicle;
use Modules\Vrc\Services\Contracts\AuditServiceContract;
use Modules\Vrc\Services\Finance\TotalsBuilder;
use Modules\Vrc\Services\Settings\VrcSettings;

/**
 * Orchestrates the repair-order lifecycle (spec §4.3, §6.4): creation,
 * server-side totals recomputation, and guarded status transitions (legal
 * graph + data rules), each writing status history and the ERP audit log.
 *
 * Data guards layer onto the graph as their dependencies land:
 *   - in_progress   → requires ≥1 assigned technician                 (Phase 4)
 *   - quality_check → all service lines done/skipped                  (Phase 4)
 *   - checked_in    → intake DVI complete or vrc.inspection.skip      (Phase 5)
 *   - estimate_sent → ≥1 line + computed total                        (Phase 6, partial here)
 *   - invoiced      → ERP invoice generated                           (Phase 7)
 *   - completed     → ERP invoice paid or deliver_with_balance perm   (Phase 7)
 */
class RepairOrderService
{
    public function __construct(
        protected RepairOrderStateMachine $machine,
        protected TotalsBuilder $totals,
        protected AuditServiceContract $audit,
        protected Util $util
    ) {
    }

    /** Expose the state machine (allowed transitions for the UI). */
    public function machine(): RepairOrderStateMachine
    {
        return $this->machine;
    }

    /** Create a draft/checked-in repair order. */
    public function create(int $businessId, array $attrs, ?int $userId): VrcRepairOrder
    {
        $attrs['business_id'] = $businessId;
        $attrs['created_by'] = $userId;
        $attrs['number'] = $attrs['number'] ?? $this->nextNumber($businessId);
        $attrs['status'] = $attrs['status'] ?? $this->machine->initialStatus();
        $attrs['tracking_token'] = $attrs['tracking_token'] ?? \Illuminate\Support\Str::random(40);

        $ro = VrcRepairOrder::create($attrs);

        $this->recordHistory($ro, null, $ro->status, $userId, $attrs['_reason'] ?? null);
        $this->audit->log($ro, 'created', ['status' => $ro->status], $businessId);

        return $ro;
    }

    /** Create a checked-in repair order from a confirmed appointment. */
    public function createFromAppointment(VrcAppointment $appt, ?int $userId): VrcRepairOrder
    {
        $vehicle = $appt->vehicle_id ? VrcVehicle::find($appt->vehicle_id) : null;

        return $this->create($appt->business_id, [
            'location_id'       => $appt->location_id,
            'erp_customer_id'   => $appt->erp_customer_id ?: optional($vehicle)->erp_customer_id,
            'vehicle_id'        => $appt->vehicle_id,
            'appointment_id'    => $appt->id,
            'bay_id'            => $appt->bay_id,
            'status'            => 'checked_in',
            'checked_in_at'     => now(),
            'customer_concerns' => $appt->concern,
            'odometer_in'       => optional($vehicle)->odometer,
        ], $userId);
    }

    /**
     * Recompute and persist subtotal / discount / tax / grand_total from the
     * order's service and part lines, via the ERP tax engine. Server-side only.
     */
    public function recomputeTotals(VrcRepairOrder $ro): VrcRepairOrder
    {
        $lines = [];

        foreach ($ro->services()->get() as $s) {
            $lines[] = [
                'qty' => (float) $s->qty, 'unit_price' => (float) $s->unit_price,
                'discount' => (float) $s->discount, 'discount_type' => $s->discount_type,
                'tax_rate_id' => $s->tax_rate_id,
            ];
        }
        foreach ($ro->parts()->get() as $p) {
            $lines[] = [
                'qty' => (float) $p->qty, 'unit_price' => (float) $p->unit_price,
                'discount' => (float) $p->discount, 'discount_type' => $p->discount_type,
                'tax_rate_id' => $p->tax_rate_id,
            ];
        }

        $totals = $this->totals->build((int) $ro->business_id, $lines);

        $ro->update([
            'subtotal'       => $totals['subtotal'],
            'discount_total' => $totals['discount_total'],
            'tax_total'      => $totals['tax_total'],
            'grand_total'    => $totals['grand_total'],
        ]);

        return $ro;
    }

    /**
     * Transition a repair order to $to, enforcing the legal graph and the
     * active data guards. Writes status history + audit. Stamps timestamps.
     *
     * @throws InvalidTransitionException  on an illegal graph move
     * @throws TransitionGuardException    when a data rule fails
     */
    public function transition(VrcRepairOrder $ro, string $to, ?int $userId, ?string $reason = null): VrcRepairOrder
    {
        $from = $ro->status;
        $this->machine->assertCanTransition($from, $to);
        $this->assertGuards($ro, $to);

        return DB::transaction(function () use ($ro, $from, $to, $userId, $reason) {
            // Generating the ERP invoice happens as part of the `invoiced`
            // transition so erp_invoice_id is set before the status flips.
            if ($to === 'invoiced' && ! $ro->erp_invoice_id
                && interface_exists(\Modules\Vrc\Services\Contracts\InvoiceServiceContract::class)) {
                app(\Modules\Vrc\Services\Contracts\InvoiceServiceContract::class)
                    ->generateFromRepairOrder($ro, $userId);
                $ro->refresh();
            }

            // Auto-create part warranties once the order is invoiced.
            if ($to === 'invoiced' && class_exists(\Modules\Vrc\Services\Warranty\WarrantyService::class)
                && \Illuminate\Support\Facades\Schema::hasTable('vrc_warranties')) {
                app(\Modules\Vrc\Services\Warranty\WarrantyService::class)->createForOrder($ro);
            }

            $stamps = $this->timestampsFor($to);
            $ro->update(array_merge(['status' => $to], $stamps));

            $this->recordHistory($ro, $from, $to, $userId, $reason);
            $this->audit->log($ro, 'transitioned', ['from' => $from, 'to' => $to, 'reason' => $reason], (int) $ro->business_id);
            $this->notifyMatrix($ro, $to);

            return $ro;
        });
    }

    /** Active data-rule guards (graph legality is checked separately). */
    protected function assertGuards(VrcRepairOrder $ro, string $to): void
    {
        if ($to === 'in_progress') {
            if (count($ro->assignedTechnicianIds()) < 1) {
                throw new TransitionGuardException(__('vrc::lang.guard_need_technician'));
            }
        }

        if ($to === 'quality_check') {
            $open = $ro->services()->whereNotIn('status', ['done', 'skipped'])->count();
            if ($ro->services()->count() > 0 && $open > 0) {
                throw new TransitionGuardException(__('vrc::lang.guard_services_incomplete'));
            }
        }

        if ($to === 'completed') {
            // The ERP invoice must be fully paid, unless the user may deliver
            // with an outstanding balance (creates an ERP receivable). Only
            // enforced once invoicing exists (Phase 7+).
            if ($ro->erp_invoice_id && interface_exists(\Modules\Vrc\Services\Contracts\InvoiceServiceContract::class)) {
                $snapshot = app(\Modules\Vrc\Services\Contracts\InvoiceServiceContract::class)->statusForOrder($ro);
                $paid = ($snapshot['status'] ?? null) === 'paid' || ($snapshot['due'] ?? 1) <= 0;
                $mayDeliverWithBalance = auth()->check() && auth()->user()->can('vrc.repair_order.deliver_with_balance');
                if (! $paid && ! $mayDeliverWithBalance) {
                    throw new TransitionGuardException(__('vrc::lang.guard_unpaid_balance'));
                }
            }
        }

        if ($to === 'ready_for_pickup') {
            // A completed final DVI is required before handover (spec §6.5),
            // but only enforced once the inspections table exists (Phase 5+).
            if (class_exists(\Modules\Vrc\Entities\VrcInspection::class)
                && \Illuminate\Support\Facades\Schema::hasTable('vrc_inspections')) {
                $hasFinal = \Modules\Vrc\Entities\VrcInspection::where('repair_order_id', $ro->id)
                    ->where('type', 'final')->where('status', 'completed')->exists();
                if (! $hasFinal) {
                    throw new TransitionGuardException(__('vrc::lang.guard_need_final_dvi'));
                }
            }
        }
    }

    protected function timestampsFor(string $to): array
    {
        return match ($to) {
            'checked_in'       => ['checked_in_at' => now()],
            'in_progress'      => ['work_started_at' => now()],
            'completed'        => ['completed_at' => now()],
            'ready_for_pickup' => [],
            default            => [],
        };
    }

    protected function recordHistory(VrcRepairOrder $ro, ?string $from, string $to, ?int $userId, ?string $reason): void
    {
        VrcRepairOrderStatusHistory::create([
            'business_id'     => $ro->business_id,
            'repair_order_id' => $ro->id,
            'from_status'     => $from,
            'to_status'       => $to,
            'actor_id'        => $userId,
            'reason'          => $reason,
            'created_at'      => now(),
        ]);
    }

    /**
     * Fire the configured customer notification for a transition (spec §6.12
     * comms matrix). Best-effort: only when the event is enabled in settings
     * and the notification contract is bound; never breaks the transition.
     */
    protected function notifyMatrix(VrcRepairOrder $ro, string $to): void
    {
        $eventByStatus = [
            'estimate_sent'    => 'estimate_sent',
            'ready_for_pickup' => 'ready_for_pickup',
            'invoiced'         => 'invoice_issued',
        ];
        $event = $eventByStatus[$to] ?? null;
        if (! $event || ! $ro->erp_customer_id) {
            return;
        }

        try {
            $matrix = (array) VrcSettings::get((int) $ro->business_id, 'comms_matrix', []);
            if (empty($matrix[$event])) {
                return;
            }
            if (! interface_exists(\Modules\Vrc\Services\Contracts\NotificationServiceContract::class)) {
                return;
            }
            $body = __('vrc::lang.notify_' . $event, [
                'ro' => $ro->number ?: ('#' . $ro->id),
            ]);
            $channel = VrcSettings::get((int) $ro->business_id, 'comms_channel', 'sms');
            app(\Modules\Vrc\Services\Contracts\NotificationServiceContract::class)
                ->send((int) $ro->business_id, (int) $ro->erp_customer_id, $channel, $body, [
                    'ro_number'  => $ro->number,
                    'location_id' => $ro->location_id,
                    'dedupe_key' => 'vrc_ro_' . $event . ':' . $ro->id,
                ]);
        } catch (\Throwable $e) {
            // Best-effort: a notification failure must never break a transition.
        }
    }

    /** Next RO number via the ERP numbering service (type vrc_ro, prefix RO-). */
    public function nextNumber(int $businessId): string
    {
        $count = $this->util->setAndGetReferenceCount('vrc_ro', $businessId);

        return $this->util->generateReferenceNumber('vrc_ro', $count, $businessId, 'RO-');
    }

    public function defaultLaborRate(int $businessId): float
    {
        return (float) VrcSettings::get($businessId, 'default_labor_rate', 0);
    }
}
