<?php
// Prevent direct access
if (count(get_included_files()) === 1) {
    http_response_code(403);
    exit('Forbidden');
}

/**
 * BillingService — gestión de cobros, estados y comisiones.
 *
 * Soporta tres vías:
 *   stripe — Stripe Billing/Checkout (LLC de US). Webhooks actualizan estado.
 *   cash   — activación manual por admin/vendedor, con current_period_end.
 *   bac    — placeholder para link de pago BAC (futuro), detrás de flag.
 *
 * Flujo de estados: trial → free → active → past_due → canceled
 */
class BillingService {

    // ── Stripe ────────────────────────────────────────────────────────────────

    /**
     * Genera una URL de Stripe Checkout para el tenant.
     * Requiere STRIPE_SECRET_KEY y STRIPE_PRICE_{PLAN} en .env.
     */
    public static function stripeCheckoutUrl(PDO $db, array $tenant, string $plan): string {
        $key = getenv('STRIPE_SECRET_KEY');
        if (!$key) throw new RuntimeException('STRIPE_SECRET_KEY not set');

        $limits  = require dirname(__DIR__, 2) . '/config/plan_limits.php';
        $precio  = $limits[$plan]['precio'] ?? null;
        if (!$precio) throw new RuntimeException("Plan $plan no tiene precio fijo");

        $priceId = getenv('STRIPE_PRICE_' . strtoupper($plan));
        if (!$priceId) throw new RuntimeException("STRIPE_PRICE_$plan not set in .env");

        // Crear o recuperar customer de Stripe
        $customerId = $tenant['stripe_customer_id'] ?? null;
        if (!$customerId) {
            $stmt = $db->prepare("SELECT u.email, u.name FROM users u WHERE u.tenant_id = :tid AND u.role = 'owner' LIMIT 1");
            $stmt->execute([':tid' => $tenant['id']]);
            $owner = $stmt->fetch();

            $resp = self::stripeRequest('POST', '/customers', [
                'email'    => $owner['email'] ?? '',
                'name'     => $owner['name']  ?? '',
                'metadata' => ['tenant_id' => $tenant['id']],
            ]);
            $customerId = $resp['id'];
            $db->prepare("UPDATE tenants SET stripe_customer_id = :cid WHERE id = :id")
               ->execute([':cid' => $customerId, ':id' => $tenant['id']]);
        }

        $baseUrl  = getenv('APP_URL') ?: 'https://valeai.app';
        $checkout = self::stripeRequest('POST', '/checkout/sessions', [
            'customer'            => $customerId,
            'mode'                => 'subscription',
            'line_items'          => [['price' => $priceId, 'quantity' => 1]],
            'success_url'         => $baseUrl . '/billing/success?session_id={CHECKOUT_SESSION_ID}',
            'cancel_url'          => $baseUrl . '/ajustes',
            'metadata'            => ['tenant_id' => $tenant['id'], 'plan' => $plan],
            'allow_promotion_codes' => true,
        ]);

        return $checkout['url'];
    }

    /**
     * Procesa un evento de webhook de Stripe.
     * Llamar desde /webhook/stripe (POST, raw body).
     * Retorna true si el evento fue procesado, false si es ignorado/duplicado.
     */
    public static function handleStripeWebhook(PDO $db, string $rawBody, string $sigHeader): bool {
        $secret = getenv('STRIPE_WEBHOOK_SECRET');
        if (!$secret) throw new RuntimeException('STRIPE_WEBHOOK_SECRET not set');

        // Verificar firma
        $event = self::stripeVerifySignature($rawBody, $sigHeader, $secret);
        $eventId = $event['id'];

        // Idempotencia: ignorar eventos ya procesados
        $stmt = $db->prepare("SELECT event_id FROM stripe_events WHERE event_id = :eid");
        $stmt->execute([':eid' => $eventId]);
        if ($stmt->fetch()) return false;

        $handled = false;
        $type    = $event['type'];
        $obj     = $event['data']['object'];

        switch ($type) {
            case 'checkout.session.completed':
                $handled = self::onCheckoutCompleted($db, $obj);
                break;

            case 'invoice.paid':
                $handled = self::onInvoicePaid($db, $obj);
                break;

            case 'invoice.payment_failed':
                $handled = self::onInvoicePaymentFailed($db, $obj);
                break;

            case 'customer.subscription.deleted':
                $handled = self::onSubscriptionDeleted($db, $obj);
                break;

            case 'customer.subscription.updated':
                $handled = self::onSubscriptionUpdated($db, $obj);
                break;
        }

        // Marcar como procesado
        $db->prepare("INSERT IGNORE INTO stripe_events (event_id) VALUES (:eid)")
           ->execute([':eid' => $eventId]);

        return $handled;
    }

    // ── Cash (activación manual) ───────────────────────────────────────────────

    /**
     * Activa un tenant con cobro en efectivo/transferencia.
     * Solo ejecutable por admin o seller asignado.
     * $mesesPagados: cuántos meses cubre el pago recibido.
     */
    public static function activarCash(PDO $db, int $tenantId, string $plan, int $mesesPagados = 1, ?int $sellerId = null): void {
        $limits  = require dirname(__DIR__, 2) . '/config/plan_limits.php';
        $precio  = $limits[$plan]['precio'] ?? 0;
        $mrr     = $precio;

        $periodEnd = date('Y-m-d', strtotime("+{$mesesPagados} months"));

        $db->prepare(
            "UPDATE tenants
             SET plan = :plan,
                 billing_method = 'cash',
                 billing_status = 'active',
                 current_period_end = :end,
                 mrr = :mrr,
                 setup_paid = 1
             WHERE id = :id"
        )->execute([
            ':plan' => $plan,
            ':end'  => $periodEnd,
            ':mrr'  => $mrr,
            ':id'   => $tenantId,
        ]);

        // Registrar comisión de MRR si hay vendedor
        if ($sellerId) {
            self::registrarComisionMrr($db, $sellerId, $tenantId, $mrr);
        }

        error_log("[Billing] Cash activation: tenant=$tenantId plan=$plan period_end=$periodEnd");
    }

    /**
     * Expirar tenants cash con current_period_end vencido.
     * Ejecutar vía cron diariamente.
     */
    public static function expirarCashVencidos(PDO $db): int {
        $stmt = $db->prepare(
            "UPDATE tenants
             SET billing_status = 'past_due'
             WHERE billing_method = 'cash'
               AND billing_status = 'active'
               AND current_period_end < CURDATE()
               AND deleted_at IS NULL"
        );
        $stmt->execute();
        return $stmt->rowCount();
    }

    // ── BAC placeholder ────────────────────────────────────────────────────────

    /**
     * Retorna la URL de pago BAC para el tenant.
     * Flag BAC_ENABLED=true en .env para activar. Hoy solo devuelve null.
     */
    public static function bacPaymentUrl(array $tenant, string $plan): ?string {
        if (!getenv('BAC_ENABLED')) return null;
        // TODO: implementar integración BAC cuando esté disponible.
        // $bacMerchantId = getenv('BAC_MERCHANT_ID');
        // $bacKey        = getenv('BAC_SECRET_KEY');
        // ... construir payload y URL de redirección BAC ...
        return null;
    }

    // ── Comisiones ─────────────────────────────────────────────────────────────

    /**
     * Registra la comisión de setup para un vendedor.
     * setupFee: monto cobrado por la puesta en marcha.
     */
    public static function registrarComisionSetup(PDO $db, int $sellerId, int $tenantId, float $setupFee): void {
        $stmt = $db->prepare("SELECT comision_setup_pct FROM sellers WHERE id = :id LIMIT 1");
        $stmt->execute([':id' => $sellerId]);
        $seller = $stmt->fetch();
        if (!$seller) return;

        $monto = round($setupFee * ($seller['comision_setup_pct'] / 100), 2);
        if ($monto <= 0) return;

        $db->prepare(
            "INSERT INTO commissions (seller_id, tenant_id, tipo, monto, periodo)
             VALUES (:sid, :tid, 'setup', :monto, NULL)"
        )->execute([':sid' => $sellerId, ':tid' => $tenantId, ':monto' => $monto]);
    }

    /**
     * Registra filas de comisión MRR por los próximos N meses.
     * Se llama cada vez que un pago MRR se confirma.
     */
    public static function registrarComisionMrr(PDO $db, int $sellerId, int $tenantId, float $mrr): void {
        $stmt = $db->prepare("SELECT comision_mrr_pct, comision_meses FROM sellers WHERE id = :id LIMIT 1");
        $stmt->execute([':id' => $sellerId]);
        $seller = $stmt->fetch();
        if (!$seller) return;

        $monto = round($mrr * ($seller['comision_mrr_pct'] / 100), 2);
        if ($monto <= 0) return;

        // Insertar una fila por el período actual (idempotente por periodo+seller+tenant)
        $periodo = date('Y-m');
        $db->prepare(
            "INSERT IGNORE INTO commissions (seller_id, tenant_id, tipo, monto, periodo)
             VALUES (:sid, :tid, 'mrr', :monto, :periodo)"
        )->execute([':sid' => $sellerId, ':tid' => $tenantId, ':monto' => $monto, ':periodo' => $periodo]);
    }

    // ── Helpers internos ───────────────────────────────────────────────────────

    private static function onCheckoutCompleted(PDO $db, array $obj): bool {
        $tenantId = (int)($obj['metadata']['tenant_id'] ?? 0);
        $plan     = $obj['metadata']['plan'] ?? 'basic';
        $subId    = $obj['subscription'] ?? null;
        if (!$tenantId) return false;

        $limits = require dirname(__DIR__, 2) . '/config/plan_limits.php';
        $mrr    = $limits[$plan]['precio'] ?? 0;

        $db->prepare(
            "UPDATE tenants
             SET plan = :plan, billing_method = 'stripe', billing_status = 'active',
                 mrr = :mrr, setup_paid = 1, stripe_sub_id = :sub
             WHERE id = :id"
        )->execute([':plan' => $plan, ':mrr' => $mrr, ':sub' => $subId, ':id' => $tenantId]);

        // Comisión MRR al vendedor asignado
        $stmtSeller = $db->prepare("SELECT seller_id FROM tenants WHERE id = :id AND deleted_at IS NULL");
        $stmtSeller->execute([':id' => $tenantId]);
        $t = $stmtSeller->fetch();
        if ($t && $t['seller_id']) {
            self::registrarComisionMrr($db, (int)$t['seller_id'], $tenantId, $mrr);
        }

        return true;
    }

    private static function onInvoicePaid(PDO $db, array $obj): bool {
        $customerId = $obj['customer'] ?? null;
        if (!$customerId) return false;

        $periodEnd = date('Y-m-d', $obj['lines']['data'][0]['period']['end'] ?? time());

        $stmt = $db->prepare(
            "UPDATE tenants
             SET billing_status = 'active', current_period_end = :end
             WHERE stripe_customer_id = :cid"
        );
        $stmt->execute([':end' => $periodEnd, ':cid' => $customerId]);

        // Comisión MRR
        $t = $db->prepare("SELECT id, seller_id, mrr FROM tenants WHERE stripe_customer_id = :cid AND deleted_at IS NULL LIMIT 1");
        $t->execute([':cid' => $customerId]);
        $tenant = $t->fetch();
        if ($tenant && $tenant['seller_id']) {
            self::registrarComisionMrr($db, (int)$tenant['seller_id'], (int)$tenant['id'], (float)$tenant['mrr']);
        }

        return true;
    }

    private static function onInvoicePaymentFailed(PDO $db, array $obj): bool {
        $customerId = $obj['customer'] ?? null;
        if (!$customerId) return false;

        $db->prepare(
            "UPDATE tenants SET billing_status = 'past_due' WHERE stripe_customer_id = :cid"
        )->execute([':cid' => $customerId]);

        return true;
    }

    private static function onSubscriptionDeleted(PDO $db, array $obj): bool {
        $customerId = $obj['customer'] ?? null;
        if (!$customerId) return false;

        $db->prepare(
            "UPDATE tenants SET billing_status = 'canceled', mrr = 0 WHERE stripe_customer_id = :cid"
        )->execute([':cid' => $customerId]);

        return true;
    }

    private static function onSubscriptionUpdated(PDO $db, array $obj): bool {
        // Manejar cambios de plan (upgrades/downgrades vía Stripe portal)
        $customerId = $obj['customer'] ?? null;
        $status     = $obj['status']   ?? null;
        if (!$customerId) return false;

        $billingStatus = match($status) {
            'active'   => 'active',
            'past_due' => 'past_due',
            'canceled', 'unpaid' => 'canceled',
            default    => null,
        };

        if ($billingStatus) {
            $db->prepare(
                "UPDATE tenants SET billing_status = :s WHERE stripe_customer_id = :cid"
            )->execute([':s' => $billingStatus, ':cid' => $customerId]);
        }

        return true;
    }

    /** Firma HMAC-SHA256 de Stripe para verificar webhooks. */
    private static function stripeVerifySignature(string $payload, string $sigHeader, string $secret): array {
        $parts = [];
        foreach (explode(',', $sigHeader) as $part) {
            [$k, $v] = explode('=', $part, 2);
            $parts[$k][] = $v;
        }
        $timestamp = $parts['t'][0] ?? 0;
        $expected  = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);

        foreach (($parts['v1'] ?? []) as $sig) {
            if (hash_equals($expected, $sig)) {
                $data = json_decode($payload, true);
                if (!$data) throw new RuntimeException('Invalid Stripe payload');
                return $data;
            }
        }
        throw new RuntimeException('Invalid Stripe signature');
    }

    /** Wrapper HTTP para la API de Stripe. */
    private static function stripeRequest(string $method, string $endpoint, array $params = []): array {
        $key = getenv('STRIPE_SECRET_KEY');
        $url = 'https://api.stripe.com/v1' . $endpoint;

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_USERPWD        => "$key:",
            CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
        ]);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
        }

        $body = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $data = json_decode($body, true);
        if ($code >= 400) {
            throw new RuntimeException('Stripe API error: ' . ($data['error']['message'] ?? $body));
        }
        return $data;
    }
}
