<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Support\ApiResponse;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

final class CustomerController extends Controller
{
    public function index(Request $request)
    {
        $query = Customer::query()
            ->where('merchant_id', $request->attributes->get('merchant')->id)
            ->where('environment', $request->attributes->get('environment'))
            ->latest('id');

        foreach (['status', 'email', 'reference_id'] as $field) {
            if ($request->filled($field)) {
                $query->where($field, $request->string($field));
            }
        }

        if ($request->filled('search')) {
            $search = '%'.$request->string('search')->toString().'%';
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', $search)
                    ->orWhere('email', 'like', $search)
                    ->orWhere('reference_id', 'like', $search)
                    ->orWhere('public_id', 'like', $search);
            });
        }

        $page = $query->paginate(min(max($request->integer('per_page', 20), 1), 100));

        return ApiResponse::success(
            collect($page->items())->map(fn (Customer $customer) => $this->resource($customer))->values(),
            200,
            ['pagination' => ['page' => $page->currentPage(), 'per_page' => $page->perPage(), 'total' => $page->total(), 'last_page' => $page->lastPage()]]
        );
    }

    public function store(Request $request)
    {
        $data = $this->validated($request, true);

        try {
            $customer = Customer::create([
                'public_id' => 'cus_'.Str::ulid(),
                'merchant_id' => $request->attributes->get('merchant')->id,
                'environment' => $request->attributes->get('environment'),
                'reference_id' => $data['reference_id'] ?? null,
                'name' => $data['name'] ?? null,
                'email' => $data['email'] ?? null,
                'phone' => $data['phone'] ?? null,
                'status' => 'active',
                'billing_address' => $data['billing_address'] ?? null,
                'shipping_address' => $data['shipping_address'] ?? null,
                'metadata' => $data['metadata'] ?? [],
            ]);
        } catch (QueryException $e) {
            return ApiResponse::error('CUSTOMER_ALREADY_EXISTS', 'A customer with this email or reference_id already exists for this merchant.', 409);
        }

        return ApiResponse::success($this->resource($customer), 201);
    }

    public function show(Request $request, string $id)
    {
        $customer = $this->findCustomer($request, $id);
        if (! $customer) {
            return ApiResponse::error('CUSTOMER_NOT_FOUND', 'The requested customer does not exist.', 404);
        }

        return ApiResponse::success($this->resource($customer));
    }

    public function update(Request $request, string $id)
    {
        $customer = $this->findCustomer($request, $id);
        if (! $customer) {
            return ApiResponse::error('CUSTOMER_NOT_FOUND', 'The requested customer does not exist.', 404);
        }

        $data = $this->validated($request, false);

        try {
            $customer->fill($data)->save();
        } catch (QueryException $e) {
            return ApiResponse::error('CUSTOMER_ALREADY_EXISTS', 'A customer with this email or reference_id already exists for this merchant.', 409);
        }

        return ApiResponse::success($this->resource($customer->refresh()));
    }

    public function destroy(Request $request, string $id)
    {
        $customer = $this->findCustomer($request, $id);
        if (! $customer) {
            return ApiResponse::error('CUSTOMER_NOT_FOUND', 'The requested customer does not exist.', 404);
        }

        $customer->update(['status' => 'archived', 'archived_at' => now()]);

        return ApiResponse::success($this->resource($customer->refresh()));
    }

    private function validated(Request $request, bool $creating): array
    {
        $requiredWhenCreating = $creating ? 'required_without:email' : 'sometimes';

        return $request->validate([
            'reference_id' => [$requiredWhenCreating, 'nullable', 'string', 'max:100'],
            'name' => ['nullable', 'string', 'max:255'],
            'email' => [$creating ? 'required_without:reference_id' : 'sometimes', 'nullable', 'email', 'max:255'],
            'phone' => ['nullable', 'string', 'max:40'],
            'billing_address' => ['nullable', 'array'],
            'shipping_address' => ['nullable', 'array'],
            'metadata' => ['nullable', 'array'],
        ]);
    }

    private function findCustomer(Request $request, string $id): ?Customer
    {
        return Customer::query()
            ->where('public_id', $id)
            ->where('merchant_id', $request->attributes->get('merchant')->id)
            ->where('environment', $request->attributes->get('environment'))
            ->first();
    }

    private function resource(Customer $customer): array
    {
        return [
            'id' => $customer->public_id,
            'reference_id' => $customer->reference_id,
            'name' => $customer->name,
            'email' => $customer->email,
            'phone' => $customer->phone,
            'status' => $customer->status,
            'billing_address' => $customer->billing_address ?? new \stdClass(),
            'shipping_address' => $customer->shipping_address ?? new \stdClass(),
            'metadata' => $customer->metadata ?? new \stdClass(),
            'archived_at' => optional($customer->archived_at)->toISOString(),
            'created_at' => optional($customer->created_at)->toISOString(),
            'updated_at' => optional($customer->updated_at)->toISOString(),
        ];
    }
}
