<?php

namespace App\Http\Controllers\Api\V1;

use App\Domain\Blockchain\Contracts\BlockchainGateway;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Payout;
use App\Support\ApiResponse;
use App\Support\AtomicAmount;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Throwable;

final class PayoutBatchController extends Controller
{
    public function store(Request $request, BlockchainGateway $gateway)
    {
        $data = $request->validate([
            'pay_currency' => ['required', 'string', 'max:20'],
            'items' => ['required', 'array', 'min:1', 'max:1000'],
            'items.*.to_address' => ['required', 'string', 'max:255'],
            'items.*.amount' => ['required', 'string', 'regex:/^(?:0|[1-9]\d*)(?:\.\d{1,8})?$/'],
            'metadata' => ['nullable', 'array'],
        ]);

        $asset = Asset::where('code', strtoupper($data['pay_currency']))->where('enabled', true)->first();
        if (! $asset) {
            return ApiResponse::error('UNSUPPORTED_CURRENCY', 'The requested asset or network is not enabled.', 422);
        }

        $items = [];
        $total = '0';
        foreach ($data['items'] as $item) {
            try {
                $validAddress = $gateway->validateAddress($asset->network, $item['to_address']);
            } catch (Throwable) {
                $validAddress = strlen(trim((string) $item['to_address'])) >= 8;
            }
            if (! $validAddress) {
                return ApiResponse::error('VALIDATION_ERROR', 'One or more payout addresses are invalid.', 422, ['to_address' => $item['to_address']]);
            }
            $atomic = AtomicAmount::fromDecimal($item['amount'], $asset->decimals);
            $total = bcadd($total, $atomic, 0);
            $items[] = ['to_address' => $item['to_address'], 'amount_atomic' => $atomic];
        }

        $merchant = $request->attributes->get('merchant');
        $environment = $request->attributes->get('environment');

        $batchId = DB::transaction(function () use ($merchant, $environment, $asset, $items, $total, $data) {
            $batchId = DB::table('payout_batches')->insertGetId([
                'public_id' => 'pob_'.Str::ulid(),
                'merchant_id' => $merchant->id,
                'asset_id' => $asset->id,
                'environment' => $environment,
                'status' => 'pending_approval',
                'total_count' => count($items),
                'total_amount_atomic' => $total,
                'metadata' => json_encode($data['metadata'] ?? [], JSON_UNESCAPED_SLASHES),
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            foreach ($items as $item) {
                DB::table('payout_batch_items')->insert([
                    'payout_batch_id' => $batchId,
                    'to_address' => $item['to_address'],
                    'amount_atomic' => $item['amount_atomic'],
                    'status' => 'pending',
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);
            }

            return $batchId;
        });

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

    public function index(Request $request)
    {
        $batches = DB::table('payout_batches')
            ->where('merchant_id', $request->attributes->get('merchant')->id)
            ->where('environment', $request->attributes->get('environment'))
            ->latest('id')
            ->get()
            ->map(fn ($batch) => $this->resource($batch->id, false));

        return ApiResponse::success($batches);
    }

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

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

    public function approve(Request $request, string $id)
    {
        $batch = $this->findBatch($request, $id);
        if (! $batch) {
            return ApiResponse::error('PAYOUT_BATCH_NOT_FOUND', 'The requested payout batch does not exist.', 404);
        }
        if ($batch->status !== 'pending_approval') {
            return ApiResponse::error('PAYOUT_BATCH_NOT_APPROVABLE', 'Only pending approval batches can be approved.', 409);
        }

        DB::transaction(function () use ($batch, $request) {
            $items = DB::table('payout_batch_items')->where('payout_batch_id', $batch->id)->where('status', 'pending')->lockForUpdate()->get();
            foreach ($items as $item) {
                $payout = Payout::create([
                    'public_id' => 'po_'.Str::ulid(),
                    'merchant_id' => $batch->merchant_id,
                    'environment' => $batch->environment,
                    'asset_id' => $batch->asset_id,
                    'to_address' => $item->to_address,
                    'amount_atomic' => $item->amount_atomic,
                    'fee_atomic' => config('gateway.tron.default_fee_atomic', '1000000'),
                    'status' => 'pending_approval',
                    'idempotency_key' => 'batch:'.$batch->public_id.':'.$item->id,
                ]);

                DB::table('payout_batch_items')->where('id', $item->id)->update([
                    'payout_id' => $payout->id,
                    'status' => 'created',
                    'updated_at' => now(),
                ]);
            }

            DB::table('payout_batches')->where('id', $batch->id)->update([
                'status' => 'approved',
                'approved_by' => $request->attributes->get('merchant')->id,
                'approved_at' => now(),
                'updated_at' => now(),
            ]);
        });

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

    private function findBatch(Request $request, string $id): ?object
    {
        return DB::table('payout_batches')
            ->where('public_id', $id)
            ->where('merchant_id', $request->attributes->get('merchant')->id)
            ->where('environment', $request->attributes->get('environment'))
            ->first();
    }

    private function resource(int $batchId, bool $withItems = true): array
    {
        $batch = DB::table('payout_batches')
            ->join('assets', 'assets.id', '=', 'payout_batches.asset_id')
            ->where('payout_batches.id', $batchId)
            ->select('payout_batches.*', 'assets.code as asset_code', 'assets.decimals as asset_decimals', 'assets.network as asset_network')
            ->first();

        $data = [
            'id' => $batch->public_id,
            'status' => $batch->status,
            'pay_currency' => $batch->asset_code,
            'network' => $batch->asset_network,
            'total_count' => (int) $batch->total_count,
            'total_amount' => AtomicAmount::toDecimal((string) $batch->total_amount_atomic, (int) $batch->asset_decimals),
            'approved_at' => $batch->approved_at,
            'metadata' => json_decode($batch->metadata ?? '[]', true) ?: [],
            'created_at' => $batch->created_at,
        ];

        if ($withItems) {
            $data['items'] = DB::table('payout_batch_items')
                ->leftJoin('payouts', 'payouts.id', '=', 'payout_batch_items.payout_id')
                ->where('payout_batch_id', $batchId)
                ->select('payout_batch_items.*', 'payouts.public_id as payout_public_id')
                ->get()
                ->map(fn ($item) => [
                    'to_address' => $item->to_address,
                    'amount' => AtomicAmount::toDecimal((string) $item->amount_atomic, (int) $batch->asset_decimals),
                    'status' => $item->status,
                    'payout_id' => $item->payout_public_id,
                    'error' => $item->error,
                ]);
        }

        return $data;
    }
}
