<?php

namespace App\Http\Controllers;

use App\Models\Ad;
use App\Models\AdsHistory;
use App\Models\AdsPlacement;
use App\Models\IpHistory;
use App\Models\Kategori;
use App\Services\CreditService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Inertia\Response;

class LandingController extends Controller
{
    public function __construct(
        private readonly CreditService $creditService,
    ) {}

    /**
     * Rotator iklan (FR-ROT-01 .. FR-ROT-07). Implementasi penuh pada Phase 5.
     */
    public function index(Request $request, string $alias, ?string $placement = null): Response|RedirectResponse
    {
        $kategori = Kategori::where('alias', $alias)->first();

        if (! $kategori) {
            return redirect(config('ads.default_url', 'https://okeweb-id.com'));
        }

        $kategori->incrementViewed();

        $adsPlacement = $this->resolvePlacement($placement);
        $ip = $request->ip();

        // Rotasi berurutan: ambil campaign no 1,2,3,4 dst sesuai pointer kategori.
        $ad = $this->nextRotation($kategori, $ip);

        if (! $ad) {
            // Kategori ada tapi tidak ada campaign iklan aktif → redirect ke link custom admin
            // (ads.empty_kategori_url), fallback ke default_url bila belum diatur.
            return redirect(config('ads.empty_kategori_url') ?: config('ads.default_url', 'https://okeweb-id.com'));
        }

        // Catat/mutakhirkan statistik tayang per placement.
        $this->recordImpression($ad->id, $adsPlacement->id);

        // Kategori Landing Page: tampilkan halaman perantara.
        if ($kategori->jenis->value === 'Landing Page') {
            return Inertia::render('Landing/Index', [
                'kategori' => $kategori,
                'ad' => $ad,
            ]);
        }

        // Kategori Redirect: langsung catat klik & arahkan.
        return $this->go($request, $ad);
    }

    /**
     * Catat klik & redirect ke URL iklan (FR-ROT-04, FR-ROT-05).
     * Model budget harian: setiap klik valid potong 1 credit dari saldo
     * user (inline). Henti bila cap harian tercapai atau saldo habis.
     */
    public function go(Request $request, Ad $ad): RedirectResponse
    {
        $ip = $request->ip();
        $today = now()->toDateString();
        $rate = (int) config('ads.default_rate', 100);

        // Anti-fraud: 1 IP/ad/hari — klik duplikat tidak catat ulang.
        $alreadyClicked = IpHistory::alreadyClicked($ad->id, $ip, $today)->exists();

        if (! $alreadyClicked) {
            DB::transaction(function () use ($ad, $ip, $request, $today, $rate): void {
                // Lock ad agar cap check serialize (klik konkuren aman).
                $locked = Ad::whereKey($ad->id)->lockForUpdate()->first();

                // Lazy-settle: bila cron terlewat, settle di first click hari baru.
                $this->settleIfNewDay($locked, $rate, $today);

                // Sudah di-pause (manual/no_saldo/daily_cap) → tidak debit.
                if ($locked->is_paused) {
                    return;
                }

                // Saldo habis → auto-pause, tidak debit (saldo tidak negatif).
                if ($locked->user->credit < 1) {
                    $locked->update(['is_paused' => true, 'pause_reason' => 'no_saldo']);

                    return;
                }

                // Cap harian tercapai → pause untuk hari itu.
                $effCap = $locked->effectiveDailyCap($rate);
                if ($locked->clicksToday() >= $effCap) {
                    $locked->update(['is_paused' => true, 'pause_reason' => 'daily_cap']);

                    return;
                }

                // Catat klik + debit saldo 1 credit.
                IpHistory::create([
                    'ip' => $ip,
                    'ad_id' => $ad->id,
                    'browser' => $request->userAgent(),
                    'tanggal' => $today,
                ]);

                $locked->increment('klik');
                $locked->increment('klik_hari_ini');
                $locked->update(['klik_tanggal' => $today]);

                $this->creditService->subtract($locked->user, 1, [
                    'invoice' => 'KLIK-'.$ad->id.'-'.now()->format('Ymd'),
                    'keterangan' => "Klik campaign \"{$locked->judul}\"",
                ]);
            });
        }

        return redirect($ad->url);
    }

    /**
     * Settle budget harian untuk satu ad bila masuk hari baru (fallback cron).
     * Mirror SettleDailyBudgets::settle().
     */
    private function settleIfNewDay(Ad $ad, int $rate, string $today): void
    {
        if (! $ad->budget_harian) {
            return;
        }

        if ($ad->klik_tanggal?->toDateString() === $today) {
            return;
        }

        $yesterday = now()->subDay()->toDateString();
        $yesterdayClicks = $ad->klik_tanggal?->toDateString() === $yesterday
            ? $ad->klik_hari_ini
            : 0;

        $baseCap = intdiv($ad->budget_harian, $rate);
        $unused = max(0, ($baseCap + $ad->rollover_klik) - $yesterdayClicks);
        $rollover = min($baseCap, $unused);

        $update = [
            'klik_hari_ini' => 0,
            'klik_tanggal' => $today,
            'rollover_klik' => $rollover,
        ];

        if ($ad->is_paused && $ad->pause_reason === 'daily_cap') {
            $update['is_paused'] = false;
            $update['pause_reason'] = null;
        }

        $ad->update($update);
    }

    /**
     * Ambil campaign berikutnya secara round-robin berurutan (by id):
     * pointer kategori menunjuk index terakhir dilayani; naik 1 per tayang,
     * wrap ke 0 setelah campaign terakhir. Atomic via lockForUpdate agar
     * request konkuren tidak memilih index sama. Prioritaskan campaign yg
     * belum diklik IP hari ini; bila semua sudah, ulangi dari awal.
     */
    private function nextRotation(Kategori $kategori, string $ip): ?Ad
    {
        return DB::transaction(function () use ($kategori, $ip): ?Ad {
            $locked = Kategori::whereKey($kategori->id)->lockForUpdate()->first();

            // Daftar lengkap (urut id) menentukan posisi pointer — stabil meski
            // filter IP menyusutkan kandidat setelah klik terekam.
            $all = Ad::rotatable($kategori->id)->get();

            if ($all->isEmpty()) {
                return null;
            }

            $count = $all->count();

            // Pointer = index terakhir dilayani; -1 berarti belum pernah jalan.
            $pointer = (int) $locked->rotation_pointer - 1;
            $nextIndex = ($pointer + 1) % $count;

            $ad = $all->values()->get($nextIndex);

            // Campaign terpilih sudah diklik IP ini hari ini → pakai kandidat
            // belum-diklik terdekat SETELAH posisi pointer (pointer tidak berubah).
            if ($ip && IpHistory::alreadyClicked($ad->id, $ip, now()->toDateString())->exists()) {
                $fresh = Ad::rotatable($kategori->id, $ip)->get();

                if ($fresh->isNotEmpty()) {
                    $anchor = $all->values()->get($nextIndex);
                    $ad = $fresh->firstWhere('id', '>', $anchor->id) ?? $fresh->first();

                    return $ad;
                }
            }

            $locked->update(['rotation_pointer' => $nextIndex + 1]);

            return $ad;
        });
    }

    /**
     * Resolve placement dari kode; fallback ke UNKNOWN (FR-PLACE-03).
     */
    private function resolvePlacement(?string $kode): AdsPlacement
    {
        if (! $kode) {
            return AdsPlacement::find(AdsPlacement::DEFAULT_ID) ?? AdsPlacement::firstOrCreate(
                ['id' => AdsPlacement::DEFAULT_ID],
                ['kode' => AdsPlacement::DEFAULT_KODE, 'keterangan' => 'Default placement']
            );
        }

        return AdsPlacement::where('kode', $kode)->first()
            ?? AdsPlacement::find(AdsPlacement::DEFAULT_ID)
            ?? AdsPlacement::firstOrCreate(
                ['id' => AdsPlacement::DEFAULT_ID],
                ['kode' => AdsPlacement::DEFAULT_KODE, 'keterangan' => 'Default placement']
            );
    }

    /**
     * Catat/mutakhirkan ads_history per placement (FR-PLACE-02)
     * + increment counter tayang (muncul total & harian) di ads.
     * Counter harian lazy-reset saat impression pertama di hari baru.
     */
    private function recordImpression(int $adId, int $placementId): void
    {
        AdsHistory::firstOrCreate(
            ['ad_id' => $adId, 'ads_placement_id' => $placementId, 'tgl_update' => now()->toDateString()],
        );

        $today = now()->toDateString();

        Ad::whereKey($adId)->update([
            'muncul' => DB::raw('muncul + 1'),
            'muncul_hari_ini' => DB::raw("CASE WHEN muncul_tanggal = '{$today}' THEN muncul_hari_ini + 1 ELSE 1 END"),
            'muncul_tanggal' => $today,
        ]);
    }
}

