<?php

namespace App\Services\AgentSettlement;

use App\Models\AgentNode;
use Illuminate\Support\Facades\DB;

final class SettlementBillGenerator
{
    /**
     * @param  array{
     *   players: array<int, array<string, mixed>>,
     *   agent_edges: array<string, int>,
     *   agent_subtrees: array<int, array<string, mixed>>,
     *   platform_share_profit?: int,
     * }  $aggregate
     * @return list<int> bill ids
     */
    public function generate(int $periodId, int $adminSiteId, array $aggregate): array
    {
        $billIds = [];
        $now = now();
        $subtrees = $aggregate['agent_subtrees'] ?? [];

        foreach ($aggregate['players'] as $playerId => $row) {
            $net = (int) $row['net_amount'];
            $billIds[] = (int) DB::table('settlement_bills')->insertGetId([
                'settlement_period_id' => $periodId,
                'bill_type' => 'player',
                'owner_type' => 'player',
                'owner_id' => $playerId,
                'counterparty_type' => 'agent',
                'counterparty_id' => (int) $row['agent_node_id'],
                'gross_win_loss' => (int) $row['game_win_loss'],
                'rebate_amount' => (int) $row['basic_rebate'] + (int) $row['extra_rebate'],
                'adjustment_amount' => 0,
                'platform_rounding_adjustment' => 0,
                'net_amount' => $net,
                'paid_amount' => 0,
                'unpaid_amount' => abs($net),
                'status' => 'pending_confirm',
                'created_at' => $now,
                'updated_at' => $now,
            ]);
        }

        foreach ($aggregate['agent_edges'] as $edge => $amount) {
            if ($amount === 0 || str_starts_with($edge, 'P_to_')) {
                continue;
            }
            $parsed = $this->parseEdge($edge);
            if ($parsed === null) {
                continue;
            }

            [$fromType, $fromId, $toType, $toId] = $parsed;
            $subtree = $subtrees[$fromId] ?? null;

            $billIds[] = (int) DB::table('settlement_bills')->insertGetId([
                'settlement_period_id' => $periodId,
                'bill_type' => 'agent',
                'owner_type' => $fromType,
                'owner_id' => $fromId,
                'counterparty_type' => $toType,
                'counterparty_id' => $toId,
                'gross_win_loss' => (int) ($subtree['gross_win_loss'] ?? 0),
                'rebate_amount' => (int) ($subtree['basic_rebate'] ?? 0) + (int) ($subtree['extra_rebate'] ?? 0),
                'adjustment_amount' => 0,
                'platform_rounding_adjustment' => 0,
                'net_amount' => $amount,
                'paid_amount' => 0,
                'unpaid_amount' => abs($amount),
                'status' => 'pending_confirm',
                'meta_json' => json_encode([
                    'edge' => $edge,
                    'share_profit' => (int) ($subtree['share_profit'] ?? 0),
                    'player_count' => (int) ($subtree['player_count'] ?? 0),
                    'platform_share_profit' => $toType === 'platform' ? (int) ($aggregate['platform_share_profit'] ?? 0) : null,
                ]),
                'created_at' => $now,
                'updated_at' => $now,
            ]);
        }

        return $billIds;
    }

    /**
     * @return array{0: string, 1: int, 2: string, 3: int}|null
     */
    private function parseEdge(string $edge): ?array
    {
        if (preg_match('/^P_to_(.+)$/', $edge, $m)) {
            $agent = AgentNode::query()->where('code', $m[1])->first();
            if ($agent === null) {
                return null;
            }

            return ['agent', (int) $agent->id, 'player', 0];
        }

        if (preg_match('/^(.+)_to_platform$/', $edge, $m)) {
            $agent = AgentNode::query()->where('code', $m[1])->first();
            if ($agent === null) {
                return null;
            }

            return ['agent', (int) $agent->id, 'platform', 0];
        }

        if (preg_match('/^(.+)_to_(.+)$/', $edge, $m)) {
            $from = AgentNode::query()->where('code', $m[1])->first();
            $to = AgentNode::query()->where('code', $m[2])->first();
            if ($from === null || $to === null) {
                return null;
            }

            return ['agent', (int) $from->id, 'agent', (int) $to->id];
        }

        return null;
    }
}
