<?php

namespace App\Services;

use App\Models\Transaction;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use InvalidArgumentException;

class TransactionFlushService
{
    public const MODES = [
        'date_range',
        'before_date',
        'older_than_years',
        'except_current_year',
        'except_current_month',
    ];

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function preview(array $payload): array
    {
        $scope = $this->resolveScope($payload);
        $query = $this->scopedQuery($scope);

        $summary = (clone $query)
            ->selectRaw('COUNT(*) as total_transactions')
            ->selectRaw('COALESCE(SUM(amount_charged), 0) as gross_sales')
            ->selectRaw('COALESCE(SUM(vendor_cost), 0) as vendor_cost_total')
            ->selectRaw('COALESCE(SUM(COALESCE(profit, CASE WHEN vendor_cost IS NOT NULL THEN amount_charged - vendor_cost ELSE 0 END)), 0) as total_profit')
            ->selectRaw("SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success_count")
            ->selectRaw("SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count")
            ->selectRaw("SUM(CASE WHEN status IN ('failed', 'reversed') THEN 1 ELSE 0 END) as failed_count")
            ->selectRaw("SUM(CASE WHEN status = 'stuck' THEN 1 ELSE 0 END) as stuck_count")
            ->first();

        $window = (clone $query)
            ->selectRaw('MIN(created_at) as oldest_tx_at')
            ->selectRaw('MAX(created_at) as newest_tx_at')
            ->first();

        return [
            'scope' => $scope,
            'total_transactions' => (int) ($summary->total_transactions ?? 0),
            'gross_sales' => round((float) ($summary->gross_sales ?? 0), 2),
            'vendor_cost_total' => round((float) ($summary->vendor_cost_total ?? 0), 2),
            'total_profit' => round((float) ($summary->total_profit ?? 0), 2),
            'success_count' => (int) ($summary->success_count ?? 0),
            'pending_count' => (int) ($summary->pending_count ?? 0),
            'failed_count' => (int) ($summary->failed_count ?? 0),
            'stuck_count' => (int) ($summary->stuck_count ?? 0),
            'oldest_tx_at' => $window?->oldest_tx_at ? Carbon::parse((string) $window->oldest_tx_at)->toDateTimeString() : null,
            'newest_tx_at' => $window?->newest_tx_at ? Carbon::parse((string) $window->newest_tx_at)->toDateTimeString() : null,
        ];
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function flush(array $payload): array
    {
        $preview = $this->preview($payload);
        if (($preview['total_transactions'] ?? 0) < 1) {
            return [
                ...$preview,
                'deleted_transactions' => 0,
            ];
        }

        $scope = (array) ($preview['scope'] ?? []);
        $query = $this->scopedQuery($scope);
        $deleted = 0;

        do {
            $batchIds = (clone $query)
                ->orderBy('id')
                ->limit(1000)
                ->pluck('id');

            if ($batchIds->isEmpty()) {
                break;
            }

            $deleted += Transaction::query()
                ->whereIn('id', $batchIds->all())
                ->delete();
        } while (true);

        return [
            ...$preview,
            'deleted_transactions' => $deleted,
        ];
    }

    /**
     * @param  array<string, mixed>  $scope
     */
    protected function scopedQuery(array $scope): Builder
    {
        $mode = (string) ($scope['flush_mode'] ?? '');
        $query = Transaction::query();

        return match ($mode) {
            'date_range' => $query->whereBetween('created_at', [
                Carbon::parse((string) $scope['start_date'])->startOfDay(),
                Carbon::parse((string) $scope['end_date'])->endOfDay(),
            ]),
            'before_date' => $query->where('created_at', '<', Carbon::parse((string) $scope['before_date'])->startOfDay()),
            'older_than_years' => $query->where('created_at', '<', now()->subYears((int) $scope['older_than_years'])->startOfDay()),
            'except_current_year' => $query->whereYear('created_at', '!=', now()->year),
            'except_current_month' => $query->where(function (Builder $builder): void {
                $builder
                    ->whereYear('created_at', '!=', now()->year)
                    ->orWhereMonth('created_at', '!=', now()->month);
            }),
            default => throw new InvalidArgumentException('Invalid transaction flush mode.'),
        };
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    protected function resolveScope(array $payload): array
    {
        $mode = strtolower((string) ($payload['flush_mode'] ?? ''));
        if (! in_array($mode, self::MODES, true)) {
            throw new InvalidArgumentException('Invalid transaction flush mode.');
        }

        return match ($mode) {
            'date_range' => $this->resolveDateRangeScope($payload),
            'before_date' => [
                'flush_mode' => 'before_date',
                'before_date' => Carbon::parse((string) ($payload['flush_before_date'] ?? now()->toDateString()))->toDateString(),
            ],
            'older_than_years' => [
                'flush_mode' => 'older_than_years',
                'older_than_years' => max(1, min(20, (int) ($payload['flush_older_than_years'] ?? 1))),
            ],
            'except_current_year' => ['flush_mode' => 'except_current_year'],
            'except_current_month' => ['flush_mode' => 'except_current_month'],
            default => throw new InvalidArgumentException('Invalid transaction flush mode.'),
        };
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    protected function resolveDateRangeScope(array $payload): array
    {
        $start = Carbon::parse((string) ($payload['flush_start_date'] ?? now()->toDateString()))->startOfDay();
        $end = Carbon::parse((string) ($payload['flush_end_date'] ?? now()->toDateString()))->endOfDay();

        if ($start->gt($end)) {
            [$start, $end] = [$end->copy()->startOfDay(), $start->copy()->endOfDay()];
        }

        $maxDays = 366;
        if ($start->diffInDays($end) > $maxDays) {
            $start = $end->copy()->subDays($maxDays)->startOfDay();
        }

        return [
            'flush_mode' => 'date_range',
            'start_date' => $start->toDateString(),
            'end_date' => $end->toDateString(),
        ];
    }
}
