<?php

declare(strict_types=1);

namespace App\Models;

use App\Core\Database;
use PDO;

/**
 * SubscriptionCodeModel
 *
 * A code the platform owner generates and hands to a company so they
 * can activate or renew their subscription without a card on file.
 * Same store-a-hash-not-the-secret pattern as ApiKeyModel — the
 * plaintext code is returned once, at creation, and never persisted.
 */
final class SubscriptionCodeModel
{
    /**
     * @param 'activate_new'|'renew_existing'|'either' $intent
     * @return array{id:int, plaintext_code:string, prefix:string}
     */
    public function create(
        string $intent,
        ?int $businessLineId,
        int $durationMonths,
        int $maxUses,
        ?string $expiresAt,
        int $createdByAdminId
    ): array {
        $code = 'SUB-' . strtoupper(bin2hex(random_bytes(8)));
        $prefix = substr($code, 0, 12);
        $hash = hash('sha256', $code);

        $stmt = Database::connection()->prepare(
            'INSERT INTO subscription_codes
                (code_prefix, code_hash, intent, business_line_id, duration_months, max_uses, expires_at, created_by_admin_id, created_at)
             VALUES (:prefix, :hash, :intent, :bl, :duration, :max_uses, :expires, :admin, NOW())'
        );
        $stmt->execute([
            'prefix' => $prefix,
            'hash' => $hash,
            'intent' => $intent,
            'bl' => $businessLineId,
            'duration' => $durationMonths,
            'max_uses' => $maxUses,
            'expires' => $expiresAt,
            'admin' => $createdByAdminId,
        ]);

        return [
            'id' => (int) Database::connection()->lastInsertId(),
            'plaintext_code' => $code,
            'prefix' => $prefix,
        ];
    }

    /** Finds an unrevoked, unexpired code with uses remaining. Does NOT consume it — call redeem() to do that. */
    public function findValidByPlaintext(string $plaintextCode): ?array
    {
        $hash = hash('sha256', trim($plaintextCode));

        $stmt = Database::connection()->prepare(
            'SELECT * FROM subscription_codes
             WHERE code_hash = :hash
               AND revoked_at IS NULL
               AND use_count < max_uses
               AND (expires_at IS NULL OR expires_at > NOW())
             LIMIT 1'
        );
        $stmt->execute(['hash' => $hash]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row === false ? null : $row;
    }

    /** Atomically consumes one use and records the redemption. Returns false if the code had no uses left (race lost). */
    public function redeem(int $codeId, int $businessLineId): bool
    {
        $db = Database::connection();
        $db->beginTransaction();

        try {
            $stmt = $db->prepare(
                'UPDATE subscription_codes
                 SET use_count = use_count + 1
                 WHERE id = :id AND use_count < max_uses AND revoked_at IS NULL'
            );
            $stmt->execute(['id' => $codeId]);

            if ($stmt->rowCount() === 0) {
                $db->rollBack();

                return false;
            }

            $db->prepare(
                'INSERT INTO subscription_code_redemptions (subscription_code_id, business_line_id, redeemed_at)
                 VALUES (:code, :bl, NOW())'
            )->execute(['code' => $codeId, 'bl' => $businessLineId]);

            $db->commit();

            return true;
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    public function revoke(int $codeId): void
    {
        Database::connection()
            ->prepare('UPDATE subscription_codes SET revoked_at = NOW() WHERE id = :id')
            ->execute(['id' => $codeId]);
    }

    /** All codes for the admin console list — never returns code_hash. */
    public function all(): array
    {
        return Database::connection()->query(
            "SELECT sc.id, sc.code_prefix, sc.intent, sc.business_line_id, bl.name AS business_line_name,
                    sc.duration_months, sc.max_uses, sc.use_count, sc.expires_at, sc.revoked_at, sc.created_at,
                    pa.name AS created_by_name
             FROM subscription_codes sc
             LEFT JOIN business_lines bl ON bl.id = sc.business_line_id
             LEFT JOIN platform_admins pa ON pa.id = sc.created_by_admin_id
             ORDER BY sc.created_at DESC"
        )->fetchAll(PDO::FETCH_ASSOC);
    }
}
