<?php

namespace App\Domains\Catalog\Services;

use App\Domains\Attachable\Services\AttachmentService;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\Product;
use App\Domains\Catalog\Models\ProductVariant;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;

class ProductService
{
    public function __construct(protected AttachmentService $attachmentService) {}
    /**
     * Create a product.
     *
     * @param array<string, mixed> $data
     */
    public function create(Tenant $tenant, array $data): Product
    {
        return DB::transaction(function () use ($tenant, $data) {
            $attributeIds = $data['attribute_ids'] ?? [];
            $images = $data['images'] ?? [];
            unset($data['attribute_ids'], $data['images']);

            /** @var Product $product */
            $product = Product::query()->create([
                ...$data,
                'tenant_codigo' => $tenant->codigo,
            ]);

            $product->attributes()->sync($attributeIds);

            if (! empty($images)) {
                $this->syncProductImages($product, $images);
            }

            return $product->load(['attributes', 'attachments', 'brand', 'category']);
        });
    }

    /**
     * Update a product.
     *
     * @param array<string, mixed> $data
     */
    public function update(Product $product, array $data): Product
    {
        return DB::transaction(function () use ($product, $data) {
            $hasAttributeIds = array_key_exists('attribute_ids', $data);
            $attributeIds = $data['attribute_ids'] ?? [];
            $hasImages = array_key_exists('images', $data);
            $images = $data['images'] ?? [];
            unset($data['attribute_ids'], $data['images']);

            // Ensure tenant_codigo cannot be updated/changed
            unset($data['tenant_codigo']);

            $product->update($data);

            if ($hasAttributeIds) {
                $product->attributes()->sync($attributeIds);
            }

            if ($hasImages) {
                $this->syncProductImages($product, $images);
            }

            return $product->load(['attributes', 'attachments', 'brand', 'category']);
        });
    }

    /**
     * Delete a product.
     */
    public function delete(Product $product): void
    {
        DB::transaction(function () use ($product) {
            foreach ($product->variants as $variant) {
                $this->deleteVariantAttachments($variant);
                $product->deleteVariant($variant);
            }

            // Delete product-level attachments from S3 and database
            $existing = $product->attachments()->get();
            $product->attachments()->detach();
            foreach ($existing as $attachment) {
                $this->attachmentService->delete($attachment);
            }

            $product->attributes()->detach();
            $product->delete();
        });
    }

    /**
     * Create a product variant.
     *
     * @param array<string, mixed> $data
     */
    public function createVariant(Product $product, array $data): ProductVariant
    {
        return DB::transaction(function () use ($product, $data) {
            $images = $data['images'] ?? [];
            unset($data['images']);

            $variant = $product->createVariant($data);

            if (! empty($images)) {
                $this->syncVariantImages($variant, $images);
            }

            return $variant->load(['product', 'definitions.attribute.options', 'attachments']);
        });
    }

    /**
     * Update a product variant.
     *
     * @param array<string, mixed> $data
     */
    public function updateVariant(ProductVariant $variant, array $data): ProductVariant
    {
        return DB::transaction(function () use ($variant, $data) {
            $hasImages = array_key_exists('images', $data);
            $images = $data['images'] ?? [];
            unset($data['images']);

            /** @var Product $product */
            $product = $variant->product;
            $updatedVariant = $product->updateVariant($variant, $data);

            if ($hasImages) {
                $this->syncVariantImages($updatedVariant, $images);
            }

            return $updatedVariant->load(['product', 'definitions.attribute.options', 'attachments']);
        });
    }

    /**
     * Delete a product variant.
     */
    public function deleteVariant(ProductVariant $variant): void
    {
        DB::transaction(function () use ($variant) {
            /** @var Product $product */
            $product = $variant->product;
            $this->deleteVariantAttachments($variant);
            $product->deleteVariant($variant);
        });
    }

    /**
     * Create an attribute.
     *
     * @param array<string, mixed> $data
     */
    public static function createAttribute(Tenant $tenant, array $data): Attribute
    {
        return DB::transaction(function () use ($tenant, $data) {
            return Product::createAttribute($tenant, $data);
        });
    }

    /**
     * Update an attribute.
     *
     * @param array<string, mixed> $data
     */
    public static function updateAttribute(Attribute $attribute, array $data): Attribute
    {
        return DB::transaction(function () use ($attribute, $data) {
            return Product::updateAttribute($attribute, $data);
        });
    }

    /**
     * Upload a list of image files/base64 strings and sync them to a variant.
     *
     * When called on update, the existing attachments are detached first so the
     * final set always matches exactly what was sent in the request.
     *
     * @param array<int, UploadedFile|string> $images
     */
    protected function syncVariantImages(ProductVariant $variant, array $images): void
    {
        $this->deleteVariantAttachments($variant);

        $attachmentIds = [];

        foreach ($images as $image) {
            $attachment = $this->attachmentService->store($image, 'variants');
            $attachmentIds[] = $attachment->id;
        }

        $variant->attachments()->sync($attachmentIds);
    }

    /**
     * Upload a list of image files/base64 strings and sync them to a product.
     *
     * Same logic as syncVariantImages but for products without variants.
     *
     * @param array<int, UploadedFile|string> $images
     */
    protected function syncProductImages(Product $product, array $images): void
    {
        // Detach pivot record and delete attachment from S3 and database
        $existing = $product->attachments()->get();
        $product->attachments()->detach();
        foreach ($existing as $attachment) {
            $this->attachmentService->delete($attachment);
        }

        $attachmentIds = [];

        foreach ($images as $image) {
            $attachment = $this->attachmentService->store($image, 'products');
            $attachmentIds[] = $attachment->id;
        }

        $product->attachments()->sync($attachmentIds);
    }

    protected function deleteVariantAttachments(ProductVariant $variant): void
    {
        $existing = $variant->attachments()->get();
        $variant->attachments()->detach();

        foreach ($existing as $attachment) {
            $this->attachmentService->delete($attachment);
        }
    }

    /**
     * Delete an attribute.
     */
    public static function deleteAttribute(Attribute $attribute): void
    {
        DB::transaction(function () use ($attribute) {
            Product::deleteAttribute($attribute);
        });
    }
}
