<?php

declare(strict_types=1);

final class ReferralCreditService
{
    public function __construct(private PDO $pdo)
    {
    }

    public function createReferralCode(int $userId): string
    {
        if ($userId <= 0) {
            throw new InvalidArgumentException('Usuario inválido para generar el código de referido.');
        }

        $stmt = $this->pdo->prepare('SELECT codigo_referido FROM usuarios WHERE id = ? LIMIT 1');
        $stmt->execute([$userId]);
        $existing = trim((string) $stmt->fetchColumn());
        if ($existing !== '') {
            return $existing;
        }

        $update = $this->pdo->prepare(
            "UPDATE usuarios SET codigo_referido = ? WHERE id = ? AND (codigo_referido IS NULL OR codigo_referido = '')"
        );

        for ($attempt = 0; $attempt < 20; $attempt++) {
            $code = 'AFX' . strtoupper(bin2hex(random_bytes(5)));
            try {
                $update->execute([$code, $userId]);
                if ($update->rowCount() > 0) {
                    return $code;
                }

                $stmt->execute([$userId]);
                $existing = trim((string) $stmt->fetchColumn());
                if ($existing !== '') {
                    return $existing;
                }
            } catch (PDOException $e) {
                if ((string) $e->getCode() !== '23000') {
                    throw $e;
                }
            }
        }

        throw new RuntimeException('No fue posible generar un código de referido único.');
    }

    public function getCurrency(): string
    {
        try {
            $stmt = $this->pdo->prepare("SELECT valor FROM configuracion_sistema WHERE clave IN ('moneda', 'currency') ORDER BY FIELD(clave, 'moneda', 'currency') LIMIT 1");
            $stmt->execute();
            $currency = strtoupper(trim((string) $stmt->fetchColumn()));
            return $currency !== '' ? $currency : 'PEN';
        } catch (Throwable) {
            return 'PEN';
        }
    }

    /**
     * Otorga una sola vez un mes adicional a la suscripción del referente.
     * El pago debe ser el primer pago mensual confirmado del referido.
     */
    public function rewardFirstPayment(int $subscriptionId, int $paymentId): bool
    {
        if ($subscriptionId <= 0 || $paymentId <= 0) {
            return false;
        }

        $ownsTransaction = !$this->pdo->inTransaction();
        if ($ownsTransaction) {
            $this->pdo->beginTransaction();
        }

        try {
            $paymentStmt = $this->pdo->prepare(
                "SELECT
                    pg.id,
                    pg.id_suscripcion,
                    pg.monto,
                    COALESCE(pg.ciclo, 'manual') AS ciclo,
                    COALESCE(pg.estado_pago, 'confirmado') AS estado_pago,
                    s.id_admin AS id_referido,
                    s.id_plan,
                    pl.nombre_plan,
                    pl.precio_mensual,
                    u.id_referidor
                 FROM pagos pg
                 JOIN suscripciones s ON s.id = pg.id_suscripcion
                 JOIN planes pl ON pl.id = s.id_plan
                 JOIN usuarios u ON u.id = s.id_admin
                 WHERE pg.id = ? AND pg.id_suscripcion = ?
                 LIMIT 1
                 FOR UPDATE"
            );
            $paymentStmt->execute([$paymentId, $subscriptionId]);
            $payment = $paymentStmt->fetch(PDO::FETCH_ASSOC);

            if (!$payment
                || ($payment['estado_pago'] ?? 'confirmado') !== 'confirmado'
                || ($payment['ciclo'] ?? 'manual') !== 'mensual'
            ) {
                if ($ownsTransaction) {
                    $this->pdo->commit();
                }
                return false;
            }

            $referredId = (int) $payment['id_referido'];
            $referrerId = (int) ($payment['id_referidor'] ?? 0);
            $monthlyPrice = (float) ($payment['precio_mensual'] ?? 0);
            $paymentAmount = (float) ($payment['monto'] ?? 0);

            if ($referrerId <= 0 || $referrerId === $referredId || $monthlyPrice <= 0 || $paymentAmount + 0.001 < $monthlyPrice) {
                if ($ownsTransaction) {
                    $this->pdo->commit();
                }
                return false;
            }

            $alreadyStmt = $this->pdo->prepare('SELECT id FROM beneficios_referidos WHERE id_referido = ? LIMIT 1 FOR UPDATE');
            $alreadyStmt->execute([$referredId]);
            if ($alreadyStmt->fetchColumn()) {
                if ($ownsTransaction) {
                    $this->pdo->commit();
                }
                return false;
            }

            $referrerSubscriptionStmt = $this->pdo->prepare(
                "SELECT id, fecha_fin, estado
                 FROM suscripciones
                 WHERE id_admin = ?
                 ORDER BY
                    CASE WHEN estado IN ('activa', 'prueba') THEN 0 ELSE 1 END,
                    fecha_fin DESC,
                    id DESC
                 LIMIT 1
                 FOR UPDATE"
            );
            $referrerSubscriptionStmt->execute([$referrerId]);
            $referrerSubscription = $referrerSubscriptionStmt->fetch(PDO::FETCH_ASSOC);

            if (!$referrerSubscription) {
                throw new RuntimeException('El referente no tiene una suscripción que pueda recibir el mes adicional.');
            }

            $now = new DateTimeImmutable('now');
            $currentEnd = !empty($referrerSubscription['fecha_fin'])
                ? new DateTimeImmutable((string) $referrerSubscription['fecha_fin'])
                : $now;
            $baseDate = $currentEnd > $now ? $currentEnd : $now;
            $newEnd = $this->addCalendarMonths($baseDate, 1);

            $updateSubscription = $this->pdo->prepare(
                "UPDATE suscripciones
                 SET fecha_fin = ?, estado = 'activa', fecha_actualizacion = NOW()
                 WHERE id = ?"
            );
            $updateSubscription->execute([
                $newEnd->format('Y-m-d H:i:s'),
                (int) $referrerSubscription['id'],
            ]);

            $insertBenefit = $this->pdo->prepare(
                "INSERT INTO beneficios_referidos
                    (id_referidor, id_referido, id_pago, monto_beneficio, estado, fecha_creacion,
                     tipo_beneficio, id_suscripcion_beneficiada, fecha_fin_anterior, fecha_fin_nueva, detalle)
                 VALUES (?, ?, ?, ?, 'otorgado', NOW(), 'mes_suscripcion', ?, ?, ?, ?)"
            );
            $insertBenefit->execute([
                $referrerId,
                $referredId,
                $paymentId,
                $monthlyPrice,
                (int) $referrerSubscription['id'],
                $currentEnd->format('Y-m-d H:i:s'),
                $newEnd->format('Y-m-d H:i:s'),
                'Se añadió un mes por el primer pago mensual confirmado del referido.',
            ]);

            if ($ownsTransaction) {
                $this->pdo->commit();
            }
            return true;
        } catch (Throwable $e) {
            if ($ownsTransaction && $this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }


    private function addCalendarMonths(DateTimeImmutable $date, int $months): DateTimeImmutable
    {
        $day = (int) $date->format('d');
        $targetFirst = $date->modify('first day of this month')->modify('+' . $months . ' month');
        $lastDay = (int) $targetFirst->format('t');
        return $targetFirst
            ->setDate((int) $targetFirst->format('Y'), (int) $targetFirst->format('m'), min($day, $lastDay))
            ->setTime((int) $date->format('H'), (int) $date->format('i'), (int) $date->format('s'));
    }

    public function getReferralDashboard(int $referrerId): array
    {
        $code = $this->createReferralCode($referrerId);

        $statsStmt = $this->pdo->prepare(
            "SELECT
                COUNT(DISTINCT u.id) AS total_referidos,
                COUNT(DISTINCT br.id) AS meses_otorgados,
                COALESCE(SUM(br.monto_beneficio), 0) AS valor_referencial
             FROM usuarios u
             JOIN roles ur ON ur.id = u.id_rol AND ur.nombre_rol IN ('Admin', 'Ayudante de Superadmin')
             LEFT JOIN beneficios_referidos br
                ON br.id_referido = u.id AND br.estado = 'otorgado'
             WHERE u.id_referidor = ?"
        );
        $statsStmt->execute([$referrerId]);
        $stats = $statsStmt->fetch(PDO::FETCH_ASSOC) ?: [];

        $rowsStmt = $this->pdo->prepare(
            "SELECT
                u.id,
                u.nombre_usuario,
                u.correo,
                u.telefono,
                COALESCE(m.saldo, 0) AS saldo,
                s.estado AS suscripcion_estado,
                s.fecha_fin,
                p.nombre_plan,
                br.estado AS beneficio_estado,
                br.fecha_creacion AS beneficio_fecha,
                br.fecha_fin_anterior,
                br.fecha_fin_nueva,
                br.monto_beneficio
             FROM usuarios u
             JOIN roles ur ON ur.id = u.id_rol AND ur.nombre_rol IN ('Admin', 'Ayudante de Superadmin')
             LEFT JOIN monederos m ON m.id_usuario = u.id
             LEFT JOIN suscripciones s
                ON s.id = (
                    SELECT s2.id
                    FROM suscripciones s2
                    WHERE s2.id_admin = u.id
                    ORDER BY s2.fecha_fin DESC, s2.id DESC
                    LIMIT 1
                )
             LEFT JOIN planes p ON p.id = s.id_plan
             LEFT JOIN beneficios_referidos br ON br.id_referido = u.id
             WHERE u.id_referidor = ?
             ORDER BY u.id DESC"
        );
        $rowsStmt->execute([$referrerId]);

        return [
            'code' => $code,
            'currency' => $this->getCurrency(),
            'stats' => [
                'total_referidos' => (int) ($stats['total_referidos'] ?? 0),
                'meses_otorgados' => (int) ($stats['meses_otorgados'] ?? 0),
                'valor_referencial' => (float) ($stats['valor_referencial'] ?? 0),
            ],
            'rows' => $rowsStmt->fetchAll(PDO::FETCH_ASSOC),
        ];
    }
}
