<?php

namespace App\Services\Admin\Manage;

use App\Enums\OrderChannel;
use App\Enums\OrderStatus;
use App\Enums\PaymentType;
use App\Enums\PriceType;
use App\Enums\ProductStockQuality;
use App\Enums\Role;
use App\Models\Customer;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\ProductVariant;
use App\Models\User;
use App\Services\Concerns\HasStockAdjustment;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class TransactionService
{
    use HasStockAdjustment, RegistersMedia;

    private const SELLING_PRICE_MAP = [
        PriceType::DISTRIBUTOR->value => PriceType::DISTRIBUTOR,
        PriceType::AGEN->value => PriceType::AGEN,
        PriceType::SUB_AGEN->value => PriceType::SUB_AGEN,
        PriceType::WHOLESALE->value => PriceType::WHOLESALE,
        PriceType::RETAIL->value => PriceType::RETAIL,
        PriceType::TIKTOK->value => PriceType::TIKTOK,
        PriceType::SHOPEE->value => PriceType::SHOPEE,
    ];

    public function __construct(
        private S3PresignedService $s3Service,
    ) {}

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = [], ?User $user = null): LengthAwarePaginator
    {
        $paginator = Order::query()
            ->select(['id', 'created_by_id', 'customer_id', 'marketing_id', 'order_number', 'channel', 'price_type', 'status', 'payment_type', 'tiktok_order_id', 'shopee_order_id', 'subtotal', 'discount', 'nego_price', 'total_amount', 'cogs', 'notes', 'created_at'])
            ->with([
                'createdBy:id',
                'createdBy.userProfile:id,user_id,full_name',
                'customer:id,name',
                'marketing:id',
                'marketing.userProfile:id,user_id,full_name',
                'orderItems:id,order_id,product_variant_id,stock_quality,quantity,unit_price,subtotal',
                'orderItems.productVariant:id,product_id,name,stock,reject_stock,retail_stock',
                'orderItems.productVariant.product:id,name',
            ])
            ->when($user && $this->isMarketingUser($user), fn ($q) => $q->where('marketing_id', $user->id))
            ->when($search, function ($q) use ($search) {
                $q->whereHas('orderItems.productVariant.product', fn ($sq) => $sq->where('name', 'like', "%{$search}%"))
                    ->orWhere('order_number', 'like', "%{$search}%")
                    ->orWhere('notes', 'like', "%{$search}%");
            })
            ->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
            ->when($filters['channel'] ?? null, fn ($q, $channel) => $q->where('channel', $channel))
            ->when($filters['payment_type'] ?? null, fn ($q, $paymentType) => $q->where('payment_type', $paymentType))
            ->when($filters['customer_id'] ?? null, fn ($q, $customerId) => $q->where('customer_id', $customerId))
            ->when($filters['marketing_id'] ?? null, fn ($q, $marketingId) => $q->where('marketing_id', $marketingId))
            ->when($filters['created_by_id'] ?? null, fn ($q, $createdById) => $q->where('created_by_id', $createdById))
            ->when($filters['date_from'] ?? null, fn ($q, $dateFrom) => $q->whereDate('created_at', '>=', $dateFrom))
            ->when($filters['date_to'] ?? null, fn ($q, $dateTo) => $q->whereDate('created_at', '<=', $dateTo))
            ->orderBy($sort, $direction)
            ->paginate($perPage);

        $paginator->getCollection()->each(function (Order $order) {
            $order->status_label = $order->status->label();
            $order->payment_type_label = $order->payment_type->label();
            $order->channel_label = $order->channel->label();
            $order->price_type_label = $order->price_type->label();

            $orderMedia = $order->getFirstMedia('photos');
            $order->photo_url = $orderMedia
                ? $this->s3Service->getTemporaryUrl($orderMedia->getPath())
                : null;
            $order->photo_conversion_url = $orderMedia
                ? $this->s3Service->getTemporaryUrl($orderMedia->getPath('thumb'))
                : null;

            $order->orderItems->each(function (OrderItem $item) {
                if (! $item->productVariant) {
                    return;
                }

                $media = $item->productVariant->getFirstMedia('images');
                $item->productVariant->photo_url = $media
                    ? $this->s3Service->getTemporaryUrl($media->getPath())
                    : null;
                $item->productVariant->photo_conversion_url = $media
                    ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                    : null;
            });

            $order->profit = $order->total_amount - $order->cogs;
        });

        return $paginator;
    }

    public function getSummary(array $filters = [], ?User $user = null): array
    {
        $query = Order::query()
            ->selectRaw('COUNT(*) as total_orders')
            ->selectRaw('COALESCE(SUM(total_amount), 0) as total_amount')
            ->selectRaw('COALESCE(SUM(discount), 0) as total_discount')
            ->selectRaw('COALESCE(SUM(nego_price), 0) as total_deduction')
            ->selectRaw('COALESCE(SUM(cogs), 0) as total_cogs')
            ->when($user && $this->isMarketingUser($user), fn ($q) => $q->where('marketing_id', $user->id))
            ->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
            ->when($filters['channel'] ?? null, fn ($q, $channel) => $q->where('channel', $channel))
            ->when($filters['payment_type'] ?? null, fn ($q, $paymentType) => $q->where('payment_type', $paymentType))
            ->when($filters['customer_id'] ?? null, fn ($q, $customerId) => $q->where('customer_id', $customerId))
            ->when($filters['marketing_id'] ?? null, fn ($q, $marketingId) => $q->where('marketing_id', $marketingId))
            ->when($filters['created_by_id'] ?? null, fn ($q, $createdById) => $q->where('created_by_id', $createdById))
            ->when($filters['date_from'] ?? null, fn ($q, $dateFrom) => $q->whereDate('created_at', '>=', $dateFrom))
            ->when($filters['date_to'] ?? null, fn ($q, $dateTo) => $q->whereDate('created_at', '<=', $dateTo))
            ->first();

        return [
            'total_orders' => $query->total_orders,
            'total_amount' => $query->total_amount,
            'total_discount' => $query->total_discount,
            'total_deduction' => $query->total_deduction,
            'net_total' => $query->total_amount - $query->total_cogs,
        ];
    }

    public function getFilterOptions(): array
    {
        return [
            'statusOptions' => OrderStatus::toSelect(),
            'channelOptions' => OrderChannel::toSelect(),
            'paymentTypeOptions' => PaymentType::toSelect(),
            'customers' => Customer::query()
                ->select(['id', 'name'])
                ->orderBy('name')
                ->get(),
            'employees' => User::query()
                ->select('id')
                ->active()
                ->with('userProfile:id,user_id,full_name')
                ->orderBy('id')
                ->get()
                ->filter(fn (User $user) => $user->userProfile?->full_name)
                ->values()
                ->map(fn (User $user) => [
                    'id' => $user->id,
                    'name' => $user->userProfile->full_name,
                ]),
        ];
    }

    public function store(array $data): Order
    {
        return DB::transaction(function () use ($data) {
            $now = now();
            $stockType = $data['stock_type'] ?? ProductStockQuality::GOOD->value;
            $priceType = $data['price_type'] ?? PriceType::RETAIL->value;
            $channel = $data['channel'] ?? OrderChannel::STORE->value;
            $paymentType = $data['payment_type'] ?? PaymentType::CASH->value;

            $subtotal = 0;
            $totalCost = 0;
            $itemRows = $this->buildItemRows($data['items'], $stockType, $priceType, $now, $subtotal, $totalCost);

            $discount = (int) ($data['discount'] ?? 0);
            $negoPrice = ! empty($data['nego_price']) ? (int) $data['nego_price'] : null;
            $totalAmount = $subtotal - $discount - ($negoPrice ?? 0);

            if ($totalAmount <= 0) {
                throw ValidationException::withMessages([
                    'discount' => 'Total harga tidak boleh nol atau kurang.',
                ]);
            }

            $order = Order::create([
                'created_by_id' => auth()->id(),
                'customer_id' => $data['customer_id'] ?? null,
                'marketing_id' => $data['marketing_id'] ?? null,
                'order_number' => $this->generateOrderNumber(),
                'channel' => $channel,
                'price_type' => $priceType,
                'status' => ($data['is_completed'] ?? false) ? OrderStatus::COMPLETED : OrderStatus::PENDING,
                'payment_type' => $paymentType,
                'tiktok_order_id' => $data['tiktok_order_id'] ?? null,
                'shopee_order_id' => $data['shopee_order_id'] ?? null,
                'subtotal' => $subtotal,
                'discount' => $discount,
                'nego_price' => $negoPrice,
                'total_amount' => $totalAmount,
                'cogs' => $totalCost,
                'notes' => $data['notes'] ?? null,
            ]);

            foreach ($itemRows as &$row) {
                $row['order_id'] = $order->id;
            }
            DB::table('order_items')->insert($itemRows);

            $this->applyStock($data['items'], $stockType, -1);

            if ($paymentType !== PaymentType::CASH->value) {
                $this->syncPhoto($order, $data);
            }

            NotificationService::notify(
                roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO],
                title: 'Transaksi Baru',
                body: 'Transaksi '.$order->order_number.' sebesar Rp '.number_format($totalAmount, 0, ',', '.').' berhasil dicatat oleh '.auth()->user()->full_name.'.',
                url: route('admin.manage.transactions.index'),
            );

            return $order;
        });
    }

    public function update(Order $order, array $data): Order
    {
        return DB::transaction(function () use ($order, $data) {
            $order->load('orderItems');

            $oldStockType = $order->orderItems->first()?->stock_type?->value ?? ProductStockQuality::GOOD->value;

            $order->orderItems->each(function (OrderItem $item) use ($oldStockType) {
                $this->adjustVariantStock($item->product_variant_id, $item->quantity, 1, $oldStockType);
            });

            $order->orderItems()->delete();

            $now = now();
            $stockType = $data['stock_type'] ?? ProductStockQuality::GOOD->value;
            $priceType = $data['price_type'] ?? PriceType::RETAIL->value;

            $subtotal = 0;
            $totalCost = 0;
            $itemRows = $this->buildItemRows($data['items'], $stockType, $priceType, $now, $subtotal, $totalCost);

            $discount = (int) ($data['discount'] ?? 0);
            $negoPrice = ! empty($data['nego_price']) ? (int) $data['nego_price'] : null;
            $totalAmount = $subtotal - $discount - ($negoPrice ?? 0);

            if ($totalAmount <= 0) {
                throw ValidationException::withMessages([
                    'discount' => 'Total harga tidak boleh nol atau kurang.',
                ]);
            }

            foreach ($itemRows as &$row) {
                $row['order_id'] = $order->id;
            }
            DB::table('order_items')->insert($itemRows);

            $order->update([
                'customer_id' => $data['customer_id'] ?? null,
                'marketing_id' => $data['marketing_id'] ?? null,
                'channel' => $data['channel'] ?? $order->channel->value,
                'price_type' => $priceType,
                'status' => ($data['is_completed'] ?? false) ? OrderStatus::COMPLETED : OrderStatus::PENDING,
                'payment_type' => $data['payment_type'] ?? $order->payment_type->value,
                'tiktok_order_id' => $data['tiktok_order_id'] ?? null,
                'shopee_order_id' => $data['shopee_order_id'] ?? null,
                'subtotal' => $subtotal,
                'discount' => $discount,
                'nego_price' => $negoPrice,
                'total_amount' => $totalAmount,
                'cogs' => $totalCost,
                'notes' => $data['notes'] ?? null,
            ]);

            $this->applyStock($data['items'], $stockType, -1);

            $paymentType = $data['payment_type'] ?? $order->payment_type->value;
            if ($paymentType !== PaymentType::CASH->value) {
                $this->syncPhoto($order, $data);
            } else {
                $order->clearMediaCollection('photos');
            }

            return $order;
        });
    }

    public function destroy(Order $order): bool
    {
        return DB::transaction(function () use ($order) {
            $order->load('orderItems');

            $stockType = $order->orderItems->first()?->stock_type?->value ?? ProductStockQuality::GOOD->value;

            $order->orderItems->each(function (OrderItem $item) use ($stockType) {
                $this->adjustVariantStock($item->product_variant_id, $item->quantity, 1, $stockType);
            });

            $order->orderItems()->delete();
            $order->delete();

            return true;
        });
    }

    public function updateStatus(Order $order, string $status): Order
    {
        $order->update(['status' => $status]);

        return $order;
    }

    private function buildItemRows(array $items, string $stockType, string $priceType, $now, int &$subtotal, int &$totalCost): array
    {
        $resolvedPriceType = $stockType === ProductStockQuality::REJECT->value
            ? PriceType::REJECT
            : (self::SELLING_PRICE_MAP[$priceType] ?? PriceType::RETAIL);

        $variantIds = collect($items)->pluck('product_variant_id')->unique()->all();
        $variants = ProductVariant::query()
            ->whereKey($variantIds)
            ->with(['productPrices:id,variant_id,type,price', 'product:id,name'])
            ->get();

        $variantLabels = $variants->mapWithKeys(fn (ProductVariant $v) => [
            $v->id => $v->product->name.' - '.$v->name,
        ]);

        $prices = $variants->mapWithKeys(function (ProductVariant $variant) use ($resolvedPriceType) {
            $price = $variant->productPrices
                ->first(fn ($p) => $p->type === $resolvedPriceType);

            return [$variant->id => $price?->price ?? 0];
        });

        $capitalPrices = $variants->mapWithKeys(function (ProductVariant $variant) {
            $price = $variant->productPrices
                ->first(fn ($p) => $p->type === PriceType::CAPITAL);

            return [$variant->id => $price?->price ?? 0];
        });

        return collect($items)->map(function ($item) use ($now, $prices, $capitalPrices, $variantLabels, $stockType, &$subtotal, &$totalCost) {
            $quantity = (int) $item['quantity'];
            $unitPrice = (int) ($prices[$item['product_variant_id']] ?? 0);
            $label = $variantLabels[$item['product_variant_id']] ?? ' Produk';

            if ($unitPrice <= 0) {
                throw ValidationException::withMessages([
                    'items' => 'Harga untuk "'.$label.'" belum diatur.',
                ]);
            }

            $itemSubtotal = $unitPrice * $quantity;
            $subtotal += $itemSubtotal;

            $capitalPrice = (int) ($capitalPrices[$item['product_variant_id']] ?? 0);

            if ($capitalPrice <= 0) {
                throw ValidationException::withMessages([
                    'items' => 'Harga modal untuk "'.$label.'" belum diatur.',
                ]);
            }

            $totalCost += $capitalPrice * $quantity;

            return [
                'order_id' => null,
                'user_id' => auth()->id(),
                'product_variant_id' => $item['product_variant_id'],
                'stock_quality' => $stockType,
                'quantity' => $quantity,
                'unit_price' => $unitPrice,
                'subtotal' => $itemSubtotal,
                'created_at' => $now,
                'updated_at' => $now,
            ];
        })->toArray();
    }

    private function generateOrderNumber(): string
    {
        $prefix = 'TRX';
        $date = now()->format('ymd');
        $lastOrder = Order::withTrashed()
            ->where('order_number', 'like', "{$prefix}{$date}%")
            ->orderByDesc('order_number')
            ->first();

        if ($lastOrder) {
            $lastSequence = (int) substr($lastOrder->order_number, -4);
            $sequence = $lastSequence + 1;
        } else {
            $sequence = 1;
        }

        return $prefix.$date.str_pad($sequence, 4, '0', STR_PAD_LEFT);
    }

    private function isMarketingUser(User $user): bool
    {
        return $user->hasAnyRole([
            Role::MARKETING_OFFLINE->value,
            Role::MARKETING_ONLINE->value,
        ]);
    }
}
