<?php

declare(strict_types=1);

namespace App\Domain\Bandwidth\Services;

use App\Domain\Account\Models\Account;
use App\Domain\Domain\Models\Domain;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

/**
 * Bandwidth aggregator — tüm sorgular gerçek `bandwidth_usage` tablosundan.
 *
 * Veri yolu:
 *   cron (BandwidthRecalcCommand) → sysapi `access-log-parse` per hesap →
 *   `recordDailyUsage()` → `bandwidth_usage` tablosu.
 *
 * Eski Phase 1 mock helper'lar (mockBytesOut/In/Requests/UniqueVisitors) kaldırıldı —
 * hiçbir yerde çağrılmıyordu ve dead-code idi. Production bağlamında reseller/customer
 * dashboard'larında 0 (sahte değil) gösterir; access-log cron çalışınca dolar.
 */
class BandwidthAggregator
{
    /**
     * Record (or upsert) a daily bandwidth row.
     */
    public function recordDailyUsage(
        Account $account,
        ?Domain $domain,
        int $bytesIn,
        int $bytesOut,
        int $requests = 0,
        int $uniqueVisitors = 0
    ): void {
        DB::table('bandwidth_usage')->upsert(
            [
                'account_id' => $account->id,
                'domain_id' => $domain?->id,
                'period_type' => 'daily',
                'period_date' => now()->toDateString(),
                'bytes_in' => $bytesIn,
                'bytes_out' => $bytesOut,
                'requests_count' => $requests,
                'unique_visitors' => $uniqueVisitors,
                'created_at' => now(),
                'updated_at' => now(),
            ],
            ['account_id', 'domain_id', 'period_type', 'period_date'],
            ['bytes_in', 'bytes_out', 'requests_count', 'unique_visitors', 'updated_at']
        );
    }

    /**
     * Daily totals (all domains summed) for an account over a date range.
     *
     * @return array<int, array{date: string, bytes_in: int, bytes_out: int, requests: int}>
     */
    public function getDailyTotal(Account $account, Carbon $from, Carbon $to): array
    {
        $rows = DB::table('bandwidth_usage')
            ->where('account_id', $account->id)
            ->where('period_type', 'daily')
            ->whereBetween('period_date', [$from->toDateString(), $to->toDateString()])
            ->groupBy('period_date')
            ->orderBy('period_date')
            ->select([
                'period_date',
                DB::raw('SUM(bytes_in) as bytes_in'),
                DB::raw('SUM(bytes_out) as bytes_out'),
                DB::raw('SUM(requests_count) as requests'),
            ])
            ->get()
            ->keyBy('period_date');

        // Fill gaps with zeros so the chart always has a complete range
        $result = [];
        $cursor = $from->copy();
        while ($cursor->lte($to)) {
            $key = $cursor->toDateString();
            $row = $rows->get($key);
            $result[] = [
                'date' => $key,
                'bytes_in' => (int) ($row?->bytes_in ?? 0),
                'bytes_out' => (int) ($row?->bytes_out ?? 0),
                'requests' => (int) ($row?->requests ?? 0),
            ];
            $cursor->addDay();
        }

        return $result;
    }

    /**
     * Total bytes_out for current month (used for quota check).
     */
    public function getMonthlyTotal(Account $account, int $year, int $month): int
    {
        $from = Carbon::create($year, $month, 1)->startOfMonth()->toDateString();
        $to = Carbon::create($year, $month, 1)->endOfMonth()->toDateString();

        return (int) DB::table('bandwidth_usage')
            ->where('account_id', $account->id)
            ->where('period_type', 'daily')
            ->whereBetween('period_date', [$from, $to])
            ->sum(DB::raw('bytes_in + bytes_out'));
    }

    /**
     * Per-domain breakdown for an account over a date range.
     *
     * @return array<int, array{domain_id: int|null, domain_name: string, bytes_in: int, bytes_out: int, requests: int, pct: float}>
     */
    public function getPerDomainBreakdown(Account $account, Carbon $from, Carbon $to): array
    {
        $rows = DB::table('bandwidth_usage as bw')
            ->leftJoin('domains as d', 'd.id', '=', 'bw.domain_id')
            ->where('bw.account_id', $account->id)
            ->where('bw.period_type', 'daily')
            ->whereBetween('bw.period_date', [$from->toDateString(), $to->toDateString()])
            ->groupBy('bw.domain_id', 'd.name')
            ->orderByRaw('SUM(bw.bytes_in + bw.bytes_out) DESC')
            ->select([
                'bw.domain_id',
                'd.name as domain_name',
                DB::raw('SUM(bw.bytes_in) as bytes_in'),
                DB::raw('SUM(bw.bytes_out) as bytes_out'),
                DB::raw('SUM(bw.requests_count) as requests'),
            ])
            ->get();

        $total = $rows->sum(fn ($r) => $r->bytes_in + $r->bytes_out);

        return $rows->map(fn ($r) => [
            'domain_id' => $r->domain_id,
            'domain_name' => $r->domain_name ?? '(account-level)',
            'bytes_in' => (int) $r->bytes_in,
            'bytes_out' => (int) $r->bytes_out,
            'requests' => (int) $r->requests,
            'pct' => $total > 0 ? round(($r->bytes_in + $r->bytes_out) / $total * 100, 1) : 0.0,
        ])->values()->all();
    }

    /**
     * Server-wide daily totals (all accounts) for admin dashboard.
     *
     * @return array<int, array{date: string, bytes_in: int, bytes_out: int, requests: int}>
     */
    public function getServerWideTotal(Carbon $from, Carbon $to): array
    {
        $rows = DB::table('bandwidth_usage')
            ->where('period_type', 'daily')
            ->whereBetween('period_date', [$from->toDateString(), $to->toDateString()])
            ->groupBy('period_date')
            ->orderBy('period_date')
            ->select([
                'period_date',
                DB::raw('SUM(bytes_in) as bytes_in'),
                DB::raw('SUM(bytes_out) as bytes_out'),
                DB::raw('SUM(requests_count) as requests'),
                DB::raw('COUNT(DISTINCT account_id) as active_accounts'),
            ])
            ->get()
            ->keyBy('period_date');

        $result = [];
        $cursor = $from->copy();
        while ($cursor->lte($to)) {
            $key = $cursor->toDateString();
            $row = $rows->get($key);
            $result[] = [
                'date' => $key,
                'bytes_in' => (int) ($row?->bytes_in ?? 0),
                'bytes_out' => (int) ($row?->bytes_out ?? 0),
                'requests' => (int) ($row?->requests ?? 0),
                'active_accounts' => (int) ($row?->active_accounts ?? 0),
            ];
            $cursor->addDay();
        }

        return $result;
    }

    /**
     * Top N accounts by total bandwidth in the given range (for admin table).
     *
     * @return array<int, array{account_id: int, username: string, bytes_in: int, bytes_out: int, requests: int}>
     */
    public function getTopBandwidthAccounts(Carbon $from, Carbon $to, int $limit = 10): array
    {
        return DB::table('bandwidth_usage as bw')
            ->join('accounts as a', 'a.id', '=', 'bw.account_id')
            ->where('bw.period_type', 'daily')
            ->whereBetween('bw.period_date', [$from->toDateString(), $to->toDateString()])
            ->groupBy('bw.account_id', 'a.username', 'a.primary_domain', 'a.status')
            ->orderByRaw('SUM(bw.bytes_in + bw.bytes_out) DESC')
            ->limit($limit)
            ->select([
                'bw.account_id',
                'a.username',
                'a.primary_domain',
                'a.status',
                DB::raw('SUM(bw.bytes_in) as bytes_in'),
                DB::raw('SUM(bw.bytes_out) as bytes_out'),
                DB::raw('SUM(bw.requests_count) as requests'),
            ])
            ->get()
            ->map(fn ($r) => [
                'account_id' => $r->account_id,
                'username' => $r->username,
                'primary_domain' => $r->primary_domain,
                'status' => $r->status,
                'bytes_in' => (int) $r->bytes_in,
                'bytes_out' => (int) $r->bytes_out,
                'requests' => (int) $r->requests,
            ])
            ->values()
            ->all();
    }

}
