<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\Sale;
use App\Models\User;
use App\Models\WarehouseStock;
use App\Notifications\VoidRequestNotification;
use App\Services\StockService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class SaleController extends Controller
{
    public function __construct(private StockService $stockService) {}

    /**
     * GET /api/sales
     */
    public function index(Request $request)
    {
        $sales = Sale::with(['user:id,name', 'items', 'warehouse:id,code,name', 'voidRequestedBy:id,name', 'voidApprovedBy:id,name', 'issueMarkedBy:id,name'])
            ->when($request->search, fn($q) => $q->where(function ($inner) use ($request) {
                $inner->where('order_no', 'like', "%{$request->search}%")
                      ->orWhere('customer_name', 'like', "%{$request->search}%")
                      ->orWhereHas('user', fn($uq) => $uq->where('name', 'like', "%{$request->search}%"));
            }))
            ->when($request->channel, fn($q) => $q->where('channel', $request->channel))
            ->when($request->warehouse_id, fn($q) => $q->where('warehouse_id', $request->warehouse_id))
            ->when($request->user_id, fn($q) => $q->where('user_id', $request->user_id))
            ->when($request->date_from, fn($q) => $q->whereDate('created_at', '>=', $request->date_from))
            ->when($request->date_to,   fn($q) => $q->whereDate('created_at', '<=', $request->date_to))
            ->when($request->status, function ($q) use ($request) {
                match ($request->status) {
                    'active'   => $q->whereNull('void_status'),
                    'pending'  => $q->where('void_status', 'pending'),
                    'void'     => $q->where('void_status', 'approved'),
                    'rejected' => $q->where('void_status', 'rejected'),
                    'pecah'    => $q->where('issue_status', 'pecah'),
                    'retur'    => $q->where('issue_status', 'retur'),
                    'hilang'   => $q->where('issue_status', 'hilang'),
                    default    => null,
                };
            })
            ->latest()
            ->paginate(20);

        return response()->json($sales->through(fn($s) => $this->format($s)));
    }

    /**
     * GET /api/sales/operators
     * Daftar operator yang pernah input transaksi penjualan, untuk dropdown filter.
     */
    public function operators(Request $request)
    {
        $userIds = Sale::when($request->warehouse_id, fn($q) => $q->where('warehouse_id', $request->warehouse_id))
            ->distinct()
            ->pluck('user_id');

        $operators = User::whereIn('id', $userIds)
            ->select('id', 'name')
            ->orderBy('name')
            ->get();

        return response()->json($operators);
    }

    /**
     * POST /api/sales
     */
    public function store(Request $request)
    {
        $data = $request->validate([
            'order_no'      => ['required', 'string', 'max:50', Rule::unique('sales')->whereNull('voided_at')],
            'channel'       => ['required', 'string', 'max:100'],
            'created_at'    => ['nullable', 'date'],
            'customer_name' => ['sometimes', 'string', 'max:255'],
            'warehouse_id'  => ['required', 'exists:warehouses,id'],
            'notes'         => ['nullable', 'string'],
            'items'         => ['required', 'array', 'min:1'],
            'items.*.product_id' => ['required', 'exists:products,id'],
            'items.*.qty'        => ['required', 'integer', 'min:1'],
            'items.*.price'      => ['required', 'integer', 'min:0'],
            'items.*.is_free'    => ['sometimes', 'boolean'],
            'payment_method'     => ['nullable', 'in:cash,qris,debit,marketplace'],
            'subtotal'           => ['nullable', 'integer', 'min:0'],
            'tax_amount'         => ['nullable', 'integer', 'min:0'],
            'service_amount'     => ['nullable', 'integer', 'min:0'],
        ]);

        $this->assertStockAvailable($data['items'], $data['warehouse_id']);

        return DB::transaction(function () use ($data, $request) {
            $total = 0;
            $saleItemsData = [];

            foreach ($data['items'] as $item) {
                $product  = Product::findOrFail($item['product_id']);
                $isFree   = !empty($item['is_free']);
                $price    = $isFree ? 0 : $item['price'];
                $subtotal = $item['qty'] * $price;
                $total   += $subtotal;

                $saleItemsData[] = [
                    'product_id'   => $product->id,
                    'sku'          => $product->sku,
                    'product_name' => $product->name,
                    'qty'          => $item['qty'],
                    'price'        => $price,
                    'subtotal'     => $subtotal,
                    'is_free'      => $isFree,
                ];
            }

            $finalSubtotal = $data['subtotal'] ?? $total;
            $finalTax = $data['tax_amount'] ?? 0;
            $finalService = $data['service_amount'] ?? 0;
            $finalPaymentMethod = $data['payment_method'] ?? 'marketplace';

            $sale = new Sale([
                'order_no'      => $data['order_no'],
                'channel'       => $data['channel'],
                'customer_name' => $data['customer_name'] ?? '-',
                'user_id'       => $request->user()->id,
                'warehouse_id'  => $data['warehouse_id'],
                'total'         => $finalSubtotal + $finalTax + $finalService,
                'subtotal'      => $finalSubtotal,
                'tax_amount'    => $finalTax,
                'service_amount'=> $finalService,
                'payment_method'=> $finalPaymentMethod,
                'notes'         => $data['notes'] ?? null,
            ]);

            // created_at/updated_at are not mass-assignable (not in Sale::$fillable),
            // so Sale::create() would silently drop them and Eloquent would stamp
            // "now" instead. Set them directly so a user-submitted date sticks.
            if (isset($data['created_at'])) {
                $customDate = \Carbon\Carbon::parse($data['created_at']);
                $sale->created_at = $customDate;
                $sale->updated_at = $customDate;
            }

            $sale->save();

            $sale->items()->createMany($saleItemsData);

            // Kurangi stok di warehouse tertentu
            foreach ($saleItemsData as $item) {
                $product = Product::find($item['product_id']);
                if (!empty($item['is_free'])) {
                    $this->stockService->decreaseForPromotion($product, $item['qty'], $request->user()->id, $sale->order_no, $data['warehouse_id']);
                } else {
                    $this->stockService->decreaseForSale($product, $item['qty'], $request->user()->id, $sale->order_no, $data['warehouse_id']);
                }
            }

            return response()->json($this->format($sale->load(['user', 'items', 'warehouse'])), 201);
        });
    }

    /**
     * Pastikan stok di warehouse mencukupi untuk semua item.
     * Stok dikurangi saat penjualan (termasuk item gratis/promo), jadi qty
     * yang diminta tidak boleh melebihi stok tersedia. Ini juga memblokir
     * penjualan saat stok 0.
     *
     * @param  array  $items  daftar item: butuh product_id & qty
     * @param  array  $creditByProduct  qty yang akan dikembalikan ke stok lebih
     *         dulu (mis. item lama saat edit), dikunci per product_id
     * @throws ValidationException
     */
    private function assertStockAvailable(array $items, int $warehouseId, array $creditByProduct = []): void
    {
        // Jumlahkan qty per produk (produk yang sama bisa muncul di beberapa baris)
        $requestedQty = [];
        foreach ($items as $item) {
            $pid = $item['product_id'];
            $requestedQty[$pid] = ($requestedQty[$pid] ?? 0) + $item['qty'];
        }

        $errors = [];
        foreach ($requestedQty as $productId => $qty) {
            $available = (int) (WarehouseStock::where('warehouse_id', $warehouseId)
                ->where('product_id', $productId)
                ->value('current_stock') ?? 0);
            // Saat edit, stok item lama dikembalikan dulu sehingga menambah stok tersedia.
            $available += (int) ($creditByProduct[$productId] ?? 0);

            if ($qty > $available) {
                $name = Product::whereKey($productId)->value('name') ?? "Produk #{$productId}";
                $errors[] = "Stok tidak mencukupi untuk {$name} (tersedia {$available}, diminta {$qty}).";
            }
        }

        if (!empty($errors)) {
            throw ValidationException::withMessages(['items' => $errors]);
        }
    }

    /**
     * GET /api/sales/{id}
     */
    public function show(Sale $sale)
    {
        $sale->load(['user:id,name', 'items', 'warehouse:id,code,name', 'voidRequestedBy:id,name', 'voidApprovedBy:id,name', 'issueMarkedBy:id,name']);

        return response()->json($this->format($sale));
    }

    /**
     * PUT /api/sales/{id}
     * Hanya manager dan super_admin yang dapat mengedit pesanan.
     */
    public function update(Request $request, Sale $sale)
    {
        if (!$request->user()->hasRole(['manager', 'super_admin'])) {
            return response()->json(['message' => 'Tidak diizinkan. Hanya manager dan admin yang dapat mengedit pesanan.'], 403);
        }

        if ($sale->void_status === 'approved') {
            return response()->json(['message' => 'Tidak dapat mengedit pesanan yang sudah divoid.'], 422);
        }

        $data = $request->validate([
            'order_no'           => ['sometimes', 'string', 'max:50', Rule::unique('sales')->ignore($sale->id)->whereNull('voided_at')],
            'channel'            => ['sometimes', 'string', 'max:100'],
            'notes'              => ['nullable', 'string'],
            'created_at'         => ['nullable', 'date'],
            'items'              => ['sometimes', 'array', 'min:1'],
            'items.*.product_id' => ['required_with:items', 'exists:products,id'],
            'items.*.qty'        => ['required_with:items', 'integer', 'min:1'],
            'items.*.price'      => ['required_with:items', 'integer', 'min:0'],
            'items.*.is_free'    => ['sometimes', 'boolean'],
        ]);

        if (isset($data['items'])) {
            // Stok item lama dikembalikan saat edit, jadi diperhitungkan sebagai kredit.
            $creditByProduct = [];
            foreach ($sale->items as $oldItem) {
                $creditByProduct[$oldItem->product_id] =
                    ($creditByProduct[$oldItem->product_id] ?? 0) + $oldItem->qty;
            }
            $this->assertStockAvailable($data['items'], $sale->warehouse_id, $creditByProduct);
        }

        return DB::transaction(function () use ($data, $request, $sale) {
            // Update metadata fields
            if (isset($data['order_no']))  $sale->order_no = $data['order_no'];
            if (isset($data['channel']))   $sale->channel  = $data['channel'];
            if (array_key_exists('notes', $data)) $sale->notes = $data['notes'];
            if (!empty($data['created_at'])) {
                $sale->created_at = \Carbon\Carbon::parse($data['created_at']);
            }

            // Update items + adjust stock if items provided
            if (isset($data['items'])) {
                // Kembalikan stok dari semua item lama
                foreach ($sale->items as $oldItem) {
                    $product = Product::find($oldItem->product_id);
                    if ($product) {
                        $this->stockService->returnFromSale(
                            $product,
                            $oldItem->qty,
                            $request->user()->id,
                            'Edit #' . $sale->order_no,
                            $sale->warehouse_id
                        );
                    }
                }

                // Hapus semua item lama
                $sale->items()->delete();

                // Buat item baru dan kurangi stok
                $subtotal = 0;
                $newItems = [];

                foreach ($data['items'] as $item) {
                    $product   = Product::findOrFail($item['product_id']);
                    $isFree    = !empty($item['is_free']);
                    $price     = $isFree ? 0 : $item['price'];
                    $itemTotal = $item['qty'] * $price;
                    $subtotal += $itemTotal;

                    $newItems[] = [
                        'product_id'   => $product->id,
                        'sku'          => $product->sku,
                        'product_name' => $product->name,
                        'qty'          => $item['qty'],
                        'price'        => $price,
                        'subtotal'     => $itemTotal,
                        'is_free'      => $isFree,
                    ];

                    if ($isFree) {
                        $this->stockService->decreaseForPromotion($product, $item['qty'], $request->user()->id, $sale->order_no, $sale->warehouse_id);
                    } else {
                        $this->stockService->decreaseForSale($product, $item['qty'], $request->user()->id, $sale->order_no, $sale->warehouse_id);
                    }
                }

                $sale->items()->createMany($newItems);
                $sale->subtotal = $subtotal;
                $sale->total    = $subtotal + ($sale->tax_amount ?? 0) + ($sale->service_amount ?? 0);
            }

            $sale->save();

            return response()->json($this->format($sale->load(['user', 'items', 'warehouse'])));
        });
    }


    /**
     * DELETE /api/sales/{id}
     */
    public function destroy(Request $request, Sale $sale)
    {
        if (!$request->user()->hasRole(['manager', 'super_admin'])) {
            return response()->json(['message' => 'Tidak diizinkan.'], 403);
        }

        DB::transaction(function () use ($sale, $request) {
            if (!$sale->voided_at) {
                foreach ($sale->items as $item) {
                    $product = Product::find($item->product_id);
                    if ($product) {
                        $this->stockService->returnFromSale(
                            $product,
                            $item->qty,
                            $request->user()->id,
                            "Hapus #{$sale->order_no}",
                            $sale->warehouse_id
                        );
                    }
                }
            }
            $sale->delete();
        });

        return response()->json(['message' => 'Pesanan berhasil dihapus.']);
    }

    /**
     * POST /api/sales/{id}/request-void
     * Operator mengajukan void transaksi.
     * Stok TIDAK dikurangi di tahap ini.
     */
    public function requestVoid(Request $request, Sale $sale)
    {
        // Cek apakah sudah pernah di-void (approved)
        if ($sale->void_status === 'approved') {
            return response()->json(['message' => 'Pesanan sudah di-void sebelumnya.'], 422);
        }

        // Cek apakah sedang pending
        if ($sale->void_status === 'pending') {
            return response()->json(['message' => 'Pesanan sudah dalam proses pengajuan void.'], 422);
        }

        $data = $request->validate([
            'reason' => ['required', 'string', 'min:5'],
        ]);

        $sale->update([
            'void_status'       => 'pending',
            'void_reason'       => $data['reason'],
            'void_requested_by' => $request->user()->id,
            'void_requested_at' => now(),
            // Reset rejection fields jika sebelumnya ditolak
            'void_rejected_reason' => null,
            'void_approved_by'     => null,
            'voided_at'            => null,
        ]);

        // Kirim notifikasi ke semua manager & super_admin
        // Dibungkus try/catch agar kegagalan notifikasi tidak membatalkan pengajuan void
        try {
            $managers = User::role(['manager', 'super_admin'])->where('is_active', true)->get();
            $operatorName = $request->user()->name;

            foreach ($managers as $manager) {
                $manager->notify(new VoidRequestNotification(
                    $sale,
                    'request',
                    "Operator {$operatorName} mengajukan void untuk pesanan #{$sale->order_no}. Alasan: {$data['reason']}",
                    $operatorName,
                ));
            }
        } catch (\Throwable $e) {
            \Log::warning("VoidRequestNotification gagal untuk sale #{$sale->id}: " . $e->getMessage());
        }

        return response()->json([
            'message' => 'Pengajuan void berhasil dikirim. Menunggu persetujuan manager.',
        ]);
    }

    /**
     * POST /api/sales/{id}/approve-void
     * Manager/Super Admin menyetujui void transaksi.
     * Stok dikembalikan di tahap ini.
     */
    public function approveVoid(Request $request, Sale $sale)
    {
        if ($sale->void_status !== 'pending') {
            return response()->json(['message' => 'Pesanan tidak dalam status menunggu persetujuan void.'], 422);
        }

        DB::transaction(function () use ($sale, $request) {
            // Restore stock
            foreach ($sale->items as $item) {
                $product = Product::find($item->product_id);
                if ($product) {
                    $this->stockService->returnFromSale(
                        $product,
                        $item->qty,
                        $request->user()->id,
                        'Void #' . $sale->order_no,
                        $sale->warehouse_id
                    );
                }
            }

            $sale->update([
                'void_status'     => 'approved',
                'voided_at'       => now(),
                'void_approved_by' => $request->user()->id,
            ]);
        });

        // Kirim notifikasi ke operator yang mengajukan
        $requester = User::find($sale->void_requested_by);
        if ($requester) {
            $approverName = $request->user()->name;
            $requester->notify(new VoidRequestNotification(
                $sale,
                'approved',
                "Void pesanan #{$sale->order_no} telah disetujui oleh {$approverName}. Stok barang telah dikembalikan.",
                $approverName,
            ));
        }

        return response()->json([
            'message' => 'Void pesanan berhasil disetujui. Stok telah dikembalikan.',
        ]);
    }

    /**
     * POST /api/sales/{id}/reject-void
     * Manager/Super Admin menolak void transaksi.
     */
    public function rejectVoid(Request $request, Sale $sale)
    {
        if ($sale->void_status !== 'pending') {
            return response()->json(['message' => 'Pesanan tidak dalam status menunggu persetujuan void.'], 422);
        }

        $data = $request->validate([
            'reason' => ['required', 'string', 'min:5'],
        ]);

        $sale->update([
            'void_status'          => 'rejected',
            'void_rejected_reason' => $data['reason'],
            'void_approved_by'     => $request->user()->id,
        ]);

        // Kirim notifikasi ke operator yang mengajukan
        $requester = User::find($sale->void_requested_by);
        if ($requester) {
            $rejecterName = $request->user()->name;
            $requester->notify(new VoidRequestNotification(
                $sale,
                'rejected',
                "Void pesanan #{$sale->order_no} ditolak oleh {$rejecterName}. Alasan: {$data['reason']}",
                $rejecterName,
            ));
        }

        return response()->json([
            'message' => 'Void pesanan berhasil ditolak.',
        ]);
    }

    /**
     * POST /api/sales/{id}/mark-issue
     * Operator menandai pesanan bermasalah (pecah/retur/hilang) untuk pelacakan.
     * Tanpa approval. Khusus 'retur' stok dikembalikan ke gudang; 'pecah'/'hilang'
     * tidak mengubah stok (barang memang hilang/rusak = kerugian).
     * issue_status null = reset ke Normal.
     */
    public function markIssue(Request $request, Sale $sale)
    {
        if ($sale->void_status === 'approved') {
            return response()->json(['message' => 'Pesanan sudah di-void, tidak dapat ditandai kendala.'], 422);
        }

        $data = $request->validate([
            'issue_status' => ['nullable', 'in:pecah,retur,hilang'],
            'issue_note'   => ['nullable', 'string'],
        ]);

        $newStatus = $data['issue_status'] ?? null;
        $oldStatus = $sale->issue_status;

        if ($newStatus === $oldStatus) {
            return response()->json($this->format($sale->load(['user', 'items', 'warehouse', 'issueMarkedBy'])));
        }

        return DB::transaction(function () use ($data, $request, $sale, $newStatus, $oldStatus) {
            // Penyesuaian stok hanya untuk transisi yang menyangkut 'retur'.
            $enteringReturn = $newStatus === 'retur' && $oldStatus !== 'retur';
            $leavingReturn  = $oldStatus === 'retur' && $newStatus !== 'retur';

            if ($enteringReturn || $leavingReturn) {
                foreach ($sale->items as $item) {
                    $product = Product::find($item->product_id);
                    if (!$product) {
                        continue;
                    }
                    if ($enteringReturn) {
                        $this->stockService->returnFromSale(
                            $product, $item->qty, $request->user()->id,
                            "Retur #{$sale->order_no}", $sale->warehouse_id
                        );
                    } else {
                        // Batal retur: barang dianggap keluar lagi, stok dikurangi kembali.
                        $this->stockService->decreaseForSale(
                            $product, $item->qty, $request->user()->id,
                            "Batal Retur #{$sale->order_no}", $sale->warehouse_id
                        );
                    }
                }
            }

            $sale->update([
                'issue_status'    => $newStatus,
                'issue_note'      => $newStatus ? ($data['issue_note'] ?? null) : null,
                'issue_marked_by' => $newStatus ? $request->user()->id : null,
                'issue_marked_at' => $newStatus ? now() : null,
            ]);

            return response()->json($this->format($sale->load(['user', 'items', 'warehouse', 'issueMarkedBy'])));
        });
    }

    private function format(Sale $sale): array
    {
        return [
            'id'             => $sale->id,
            'order_no'       => $sale->order_no,
            'channel'        => $sale->channel,
            'customer_name'  => $sale->customer_name,
            'operator_name'  => $sale->user?->name ?? 'Unknown',
            'warehouse_id'   => $sale->warehouse_id,
            'warehouse_name' => $sale->warehouse?->name ?? '-',
            'items'          => $sale->items->map(fn($i) => [
                'product_id'   => $i->product_id,
                'sku'          => $i->sku,
                'product_name' => $i->product_name,
                'qty'          => $i->qty,
                'price'        => $i->price,
                'subtotal'     => $i->subtotal,
                'is_free'      => (bool) $i->is_free,
            ]),
            'subtotal'       => $sale->subtotal,
            'tax_amount'     => $sale->tax_amount,
            'service_amount' => $sale->service_amount,
            'payment_method' => $sale->payment_method,
            'total'          => $sale->total,
            'notes'          => $sale->notes,
            'voided_at'      => $sale->voided_at?->toISOString(),
            'void_reason'    => $sale->void_reason,
            'void_status'    => $sale->void_status,
            'void_requested_by_name' => $sale->voidRequestedBy?->name,
            'void_requested_at'      => $sale->void_requested_at?->toISOString(),
            'void_approved_by_name'  => $sale->voidApprovedBy?->name,
            'void_rejected_reason'   => $sale->void_rejected_reason,
            'issue_status'           => $sale->issue_status,
            'issue_note'             => $sale->issue_note,
            'issue_marked_by_name'   => $sale->issueMarkedBy?->name,
            'issue_marked_at'        => $sale->issue_marked_at?->toISOString(),
            'created_at'     => $sale->created_at->toISOString(),
            'shipped_at'     => $sale->shipped_at?->toISOString(),
            'delivered_at'   => $sale->delivered_at?->toISOString(),
        ];
    }
}
