<?php

namespace App\Services\PublicProfile;

use Config\Database;

/**
 * Enforces "max N contact detail reveals per day" for any logged-in
 * viewer (candidate or agency) browsing PUBLIC profiles — this is
 * distinct from the existing agency-side CreditService unlock flow
 * (candidate search), which already gates contact reveals by credits.
 * This limit applies specifically to the public-profile "Reveal Contact"
 * button, which has no credit cost, so it needs its own abuse guard.
 *
 * Configurable via settings group 'profile', key 'daily_contact_view_limit'
 * (default 10 per spec).
 */
class ContactViewLimitService
{
    public function getLimit(): int
    {
        $row = Database::connect()->table('settings')->where('group', 'profile')->where('key', 'daily_contact_view_limit')->get()->getRow();
        return $row ? (int) $row->value : 10;
    }

    public function checkAndRecord(int $viewerUserId): array
    {
        $limit = $this->getLimit();
        $db = Database::connect();
        $today = date('Y-m-d');

        $db->transStart();

        // Row lock to prevent a race condition letting two concurrent
        // requests both slip through at exactly the limit boundary.
        $existing = $db->query(
            'SELECT * FROM contact_view_daily_limits WHERE viewer_user_id = ? AND view_date = ? FOR UPDATE',
            [$viewerUserId, $today]
        )->getRowArray();

        $currentCount = $existing['view_count'] ?? 0;

        if ($currentCount >= $limit) {
            $db->transComplete();
            $secondsUntilReset = $this->secondsUntilNextWindow($existing['last_viewed_at'] ?? null);
            throw new \App\Exceptions\ContactViewLimitExceededException(
                'You have reached your daily contact view limit. Please wait ' . $this->formatCountdown($secondsUntilReset) . ' before viewing more contacts.',
                $secondsUntilReset
            );
        }

        if ($existing) {
            $db->table('contact_view_daily_limits')->where('id', $existing['id'])->update([
                'view_count'     => $currentCount + 1,
                'last_viewed_at' => date('Y-m-d H:i:s'),
            ]);
        } else {
            $db->table('contact_view_daily_limits')->insert([
                'viewer_user_id' => $viewerUserId,
                'view_date'      => $today,
                'view_count'     => 1,
                'last_viewed_at' => date('Y-m-d H:i:s'),
            ]);
        }

        $db->transComplete();

        return ['used' => $currentCount + 1, 'limit' => $limit, 'remaining' => $limit - ($currentCount + 1)];
    }

    public function status(int $viewerUserId): array
    {
        $limit = $this->getLimit();
        $row = Database::connect()->table('contact_view_daily_limits')
            ->where('viewer_user_id', $viewerUserId)->where('view_date', date('Y-m-d'))
            ->get()->getRowArray();

        $used = $row['view_count'] ?? 0;
        $secondsUntilReset = $used >= $limit ? $this->secondsUntilNextWindow($row['last_viewed_at'] ?? null) : null;

        return [
            'limit'     => $limit,
            'used'      => $used,
            'remaining' => max(0, $limit - $used),
            'blocked'   => $used >= $limit,
            'seconds_until_reset' => $secondsUntilReset,
        ];
    }

    /**
     * The spec's "wait 12 hours" cooldown is measured from the LAST view
     * that hit the limit, not a fixed midnight reset — matches the literal
     * "please wait 12 hours" wording rather than a calendar-day reset.
     */
    private function secondsUntilNextWindow(?string $lastViewedAt): int
    {
        if (! $lastViewedAt) {
            return 0;
        }
        $resetAt = strtotime($lastViewedAt) + (12 * 3600);
        return max(0, $resetAt - time());
    }

    private function formatCountdown(int $seconds): string
    {
        $hours = intdiv($seconds, 3600);
        $minutes = intdiv($seconds % 3600, 60);
        if ($hours > 0) {
            return "{$hours}h {$minutes}m";
        }
        return "{$minutes}m";
    }
}
