<?php

declare(strict_types=1);

$config = require __DIR__ . '/config.php';

require __DIR__ . '/lib/Db.php';
require __DIR__ . '/lib/Http.php';
require __DIR__ . '/lib/Billing.php';
require __DIR__ . '/lib/Paystack.php';
require __DIR__ . '/lib/Mnotify.php';
require __DIR__ . '/lib/Mailer.php';

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Content-Type, X-API-KEY, X-Paystack-Signature');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/';
$base = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/api/index.php')), '/');
$path = $uri;
if ($base !== '' && str_starts_with($path, $base)) {
    $path = substr($path, strlen($base));
}
$path = '/' . ltrim($path, '/');

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$pdo = Db::pdo($config['db']);

function sendPaymentReceipt(PDO $pdo, array $config, int $invoiceId, float $amount, string $method, ?string $reference = null): void
{
    try {
        $stmt = $pdo->prepare(
            "SELECT i.id AS invoice_id, i.billing_month, i.due_date, i.amount_due, i.amount_paid,
                    (i.amount_due - i.amount_paid) AS balance,
                    l.id AS learner_id, l.first_name, l.last_name, l.phone AS learner_phone, l.email AS learner_email,
                    g.id AS guardian_id, g.full_name AS guardian_name, g.phone AS guardian_phone, g.email AS guardian_email
             FROM invoices i
             JOIN learners l ON l.id = i.learner_id
             LEFT JOIN guardians g ON g.id = i.guardian_id
             WHERE i.id = ?
             LIMIT 1"
        );
        $stmt->execute([$invoiceId]);
        $r = $stmt->fetch();
        if (!$r) {
            return;
        }

        $learnerName = trim((string)$r['first_name'] . ' ' . (string)$r['last_name']);
        $paid = number_format((float)$amount, 2);
        $due = number_format((float)$r['amount_due'], 2);
        $paidTotal = number_format((float)$r['amount_paid'], 2);
        $balance = number_format(max(0.0, (float)$r['balance']), 2);
        $month = (string)$r['billing_month'];

        $smsRecipient = trim((string)($r['guardian_phone'] ?: $r['learner_phone']));
        $emailRecipient = trim((string)($r['guardian_email'] ?: $r['learner_email']));

        $ref = $reference ? (' Ref: ' . $reference) : '';
        $sms = "Superb Systems Academy: Payment received for {$learnerName} ({$month}). Paid GHS {$paid}. Total Paid GHS {$paidTotal}. Balance GHS {$balance}.{$ref}";
        $subject = 'Payment Receipt - Superb Systems Academy';
        $html = "<p>Hello,</p>"
            . "<p>We have received a payment for <strong>{$learnerName}</strong> for <strong>{$month}</strong>.</p>"
            . "<p>Amount paid: <strong>GHS {$paid}</strong></p>"
            . "<p>Invoice total due: <strong>GHS {$due}</strong></p>"
            . "<p>Total paid to date: <strong>GHS {$paidTotal}</strong></p>"
            . "<p>Outstanding balance: <strong>GHS {$balance}</strong></p>"
            . ($reference ? ("<p>Reference: <strong>" . htmlspecialchars($reference) . "</strong></p>") : '')
            . "<p>Thank you.</p>";

        $logStmt = $pdo->prepare(
            'INSERT INTO notification_logs (invoice_id, learner_id, guardian_id, channel, recipient, template_key, subject, message, provider, provider_message_id, status, error_message, sent_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
        );

        $attempts = [];
        if ($smsRecipient !== '') {
            $attempts[] = ['channel' => 'sms', 'recipient' => $smsRecipient];
        }
        if ($emailRecipient !== '') {
            $attempts[] = ['channel' => 'email', 'recipient' => $emailRecipient];
        }
        if (count($attempts) === 0) {
            return;
        }

        foreach ($attempts as $a) {
            $channel = $a['channel'];
            $recipient = $a['recipient'];

            $provider = null;
            $providerMessageId = null;
            $status = 'failed';
            $error = null;

            try {
                if ($channel === 'sms') {
                    $provider = 'mnotify';
                    $resp = Mnotify::sendSms($config['mnotify'], $recipient, $sms);
                    $providerMessageId = is_string($resp['raw'] ?? null) ? (string)$resp['raw'] : null;
                } else {
                    $provider = 'smtp';
                    Mailer::send($config['email'], $recipient, $subject, $html);
                }
                $status = 'sent';
            } catch (Throwable $e) {
                $error = $e->getMessage();
            }

            try {
                $logStmt->execute([
                    $invoiceId,
                    $r['learner_id'] !== null ? (int)$r['learner_id'] : null,
                    $r['guardian_id'] !== null ? (int)$r['guardian_id'] : null,
                    $channel,
                    $recipient,
                    'payment_receipt',
                    $channel === 'email' ? $subject : null,
                    $channel === 'email' ? $html : $sms,
                    $provider,
                    $providerMessageId,
                    $status,
                    $error,
                    $status === 'sent' ? date('Y-m-d H:i:s') : null,
                ]);
            } catch (Throwable) {
                // never block payment flow on log insert
            }
        }
    } catch (Throwable) {
        // never block payment flow on receipt
    }
}

try {
    if ($path === '/health' && $method === 'GET') {
        Http::json(['ok' => true, 'time' => date('c')]);
        exit;
    }

    if ($path !== '/paystack/webhook') {
        Http::requireApiKey($config['security']['api_key']);
    }

    if ($path === '/learners' && $method === 'GET') {
        $rows = $pdo->query(
            "SELECT l.*, g.full_name AS guardian_name, g.phone AS guardian_phone, g.email AS guardian_email
             FROM learners l
             LEFT JOIN guardians g ON g.id = l.guardian_id
             ORDER BY l.id DESC
             LIMIT 200"
        )->fetchAll();
        Http::json(['data' => $rows]);
        exit;
    }

    if ($path === '/learners' && $method === 'POST') {
        $body = Http::readJsonBody();

        $guardianId = $body['guardian_id'] ?? null;
        $learnerCode = trim((string)($body['learner_code'] ?? ''));
        $firstName = trim((string)($body['first_name'] ?? ''));
        $lastName = trim((string)($body['last_name'] ?? ''));

        if ($learnerCode === '' || $firstName === '' || $lastName === '') {
            Http::json(['error' => 'learner_code, first_name, last_name are required'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            'INSERT INTO learners (guardian_id, learner_code, first_name, last_name, date_of_birth, gender, phone, email, address, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
        );
        $stmt->execute([
            $guardianId !== null ? (int)$guardianId : null,
            $learnerCode,
            $firstName,
            $lastName,
            $body['date_of_birth'] ?? null,
            $body['gender'] ?? null,
            $body['phone'] ?? null,
            $body['email'] ?? null,
            $body['address'] ?? null,
            $body['status'] ?? 'active',
        ]);

        Http::json(['id' => (int)$pdo->lastInsertId()], 201);
        exit;
    }

    if ($path === '/discounts' && $method === 'GET') {
        $subscriptionId = (int)($_GET['subscription_id'] ?? 0);
        if ($subscriptionId <= 0) {
            Http::json(['error' => 'subscription_id is required'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            "SELECT id, subscription_id, discount_type, discount_value, start_month, end_month, reason, status, created_at
             FROM subscription_discounts
             WHERE subscription_id = ?
             ORDER BY start_month DESC, id DESC
             LIMIT 200"
        );
        $stmt->execute([$subscriptionId]);
        Http::json(['data' => $stmt->fetchAll()]);
        exit;
    }

    if ($path === '/invoices' && $method === 'GET') {
        $month = (string)($_GET['billing_month'] ?? date('Y-m'));
        if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
            Http::json(['error' => 'Invalid billing_month'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            "SELECT i.id AS invoice_id, i.billing_month, i.due_date, i.amount_due, i.amount_paid,
                    (i.amount_due - i.amount_paid) AS balance,
                    i.status,
                    l.id AS learner_id, l.learner_code, l.first_name, l.last_name, l.phone AS learner_phone, l.email AS learner_email,
                    g.id AS guardian_id, g.full_name AS guardian_name, g.phone AS guardian_phone, g.email AS guardian_email
             FROM invoices i
             JOIN learners l ON l.id = i.learner_id
             LEFT JOIN guardians g ON g.id = i.guardian_id
             WHERE i.billing_month = ?
               AND i.status <> 'void'
             ORDER BY i.due_date ASC, i.id DESC"
        );
        $stmt->execute([$month]);
        Http::json(['billing_month' => $month, 'data' => $stmt->fetchAll()]);
        exit;
    }

    if ($path === '/invoices/detail' && $method === 'GET') {
        $invoiceId = (int)($_GET['invoice_id'] ?? 0);
        if ($invoiceId <= 0) {
            Http::json(['error' => 'invoice_id is required'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            "SELECT i.*, 
                    l.learner_code, l.first_name, l.last_name, l.phone AS learner_phone, l.email AS learner_email,
                    g.full_name AS guardian_name, g.phone AS guardian_phone, g.email AS guardian_email,
                    c.name AS course_name, cs.session_name
             FROM invoices i
             JOIN learners l ON l.id = i.learner_id
             LEFT JOIN guardians g ON g.id = i.guardian_id
             JOIN course_sessions cs ON cs.id = i.course_session_id
             JOIN courses c ON c.id = cs.course_id
             WHERE i.id = ?
             LIMIT 1"
        );
        $stmt->execute([$invoiceId]);
        $inv = $stmt->fetch();
        if (!$inv) {
            Http::json(['error' => 'Invoice not found'], 404);
            exit;
        }

        $payStmt = $pdo->prepare(
            "SELECT id, amount, currency, method, channel, paystack_reference, external_id, paid_at, received_by, notes
             FROM payments
             WHERE invoice_id = ?
             ORDER BY paid_at DESC, id DESC"
        );
        $payStmt->execute([$invoiceId]);

        Http::json(['invoice' => $inv, 'payments' => $payStmt->fetchAll()]);
        exit;
    }

    if ($path === '/stats/overview' && $method === 'GET') {
        $month = (string)($_GET['billing_month'] ?? date('Y-m'));
        if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
            Http::json(['error' => 'Invalid billing_month'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            "SELECT
                COUNT(*) AS invoices_count,
                SUM(amount_due) AS total_due,
                SUM(amount_paid) AS total_paid,
                SUM(amount_due - amount_paid) AS total_balance
             FROM invoices
             WHERE billing_month = ?
               AND status <> 'void'"
        );
        $stmt->execute([$month]);
        $row = $stmt->fetch() ?: [];
        Http::json(['billing_month' => $month, 'data' => $row]);
        exit;
    }

    if ($path === '/stats/invoice-status' && $method === 'GET') {
        $month = (string)($_GET['billing_month'] ?? date('Y-m'));
        if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
            Http::json(['error' => 'Invalid billing_month'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            "SELECT status, COUNT(*) AS count
             FROM invoices
             WHERE billing_month = ?
             GROUP BY status"
        );
        $stmt->execute([$month]);
        Http::json(['billing_month' => $month, 'data' => $stmt->fetchAll()]);
        exit;
    }

    if ($path === '/stats/revenue-trend' && $method === 'GET') {
        $months = (int)($_GET['months'] ?? 6);
        $months = max(1, min(24, $months));

        $stmt = $pdo->prepare(
            "SELECT billing_month, SUM(amount_paid) AS paid
             FROM invoices
             WHERE status <> 'void'
             GROUP BY billing_month
             ORDER BY billing_month DESC
             LIMIT ?"
        );
        $stmt->bindValue(1, $months, PDO::PARAM_INT);
        $stmt->execute();
        $rows = $stmt->fetchAll();
        $rows = array_reverse($rows);
        Http::json(['months' => $months, 'data' => $rows]);
        exit;
    }

    if ($path === '/stats/new-learners' && $method === 'GET') {
        $months = (int)($_GET['months'] ?? 6);
        $months = max(1, min(24, $months));

        $stmt = $pdo->prepare(
            "SELECT DATE_FORMAT(created_at, '%Y-%m') AS ym, COUNT(*) AS count
             FROM learners
             GROUP BY ym
             ORDER BY ym DESC
             LIMIT ?"
        );
        $stmt->bindValue(1, $months, PDO::PARAM_INT);
        $stmt->execute();
        $rows = array_reverse($stmt->fetchAll());
        Http::json(['months' => $months, 'data' => $rows]);
        exit;
    }

    if ($path === '/guardians' && $method === 'POST') {
        $body = Http::readJsonBody();
        $fullName = trim((string)($body['full_name'] ?? ''));
        if ($fullName === '') {
            Http::json(['error' => 'full_name is required'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            'INSERT INTO guardians (full_name, phone, phone_alt, email, address, relationship, status) VALUES (?, ?, ?, ?, ?, ?, ?)'
        );
        $stmt->execute([
            $fullName,
            $body['phone'] ?? null,
            $body['phone_alt'] ?? null,
            $body['email'] ?? null,
            $body['address'] ?? null,
            $body['relationship'] ?? null,
            $body['status'] ?? 'active',
        ]);

        Http::json(['id' => (int)$pdo->lastInsertId()], 201);
        exit;
    }

    if ($path === '/guardians' && $method === 'GET') {
        $rows = $pdo->query(
            "SELECT id, full_name, phone, phone_alt, email, address, relationship, status, created_at
             FROM guardians
             ORDER BY id DESC
             LIMIT 500"
        )->fetchAll();
        Http::json(['data' => $rows]);
        exit;
    }

    if ($path === '/billing/generate' && $method === 'POST') {
        $body = Http::readJsonBody();
        $month = (string)($body['billing_month'] ?? '');
        if ($month === '') {
            $month = date('Y-m');
        }
        $result = Billing::ensureInvoicesForMonth($pdo, $month);
        Http::json($result);
        exit;
    }

    if ($path === '/invoices/paystack-link' && $method === 'POST') {
        $body = Http::readJsonBody();
        $invoiceId = (int)($body['invoice_id'] ?? 0);
        $email = trim((string)($body['email'] ?? ''));
        if ($invoiceId <= 0 || $email === '') {
            Http::json(['error' => 'invoice_id and email are required'], 422);
            exit;
        }

        $invStmt = $pdo->prepare(
            "SELECT i.*, l.first_name, l.last_name
             FROM invoices i
             JOIN learners l ON l.id = i.learner_id
             WHERE i.id = ?"
        );
        $invStmt->execute([$invoiceId]);
        $inv = $invStmt->fetch();
        if (!$inv) {
            Http::json(['error' => 'Invoice not found'], 404);
            exit;
        }
        if ($inv['status'] === 'paid') {
            Http::json(['error' => 'Invoice already paid'], 409);
            exit;
        }

        $amountKobo = (int) round(((float)$inv['amount_due'] - (float)$inv['amount_paid']) * 100);
        if ($amountKobo <= 0) {
            Http::json(['error' => 'No outstanding amount'], 409);
            exit;
        }

        $reference = 'SSA-' . $invoiceId . '-' . bin2hex(random_bytes(6));

        $payload = [
            'email' => $email,
            'amount' => $amountKobo,
            'currency' => $config['paystack']['currency'],
            'reference' => $reference,
            'callback_url' => $config['paystack']['callback_url'],
            'metadata' => [
                'invoice_id' => (int)$invoiceId,
                'learner_id' => (int)$inv['learner_id'],
                'billing_month' => (string)$inv['billing_month'],
                'learner_name' => trim($inv['first_name'] . ' ' . $inv['last_name']),
            ],
        ];

        $init = Paystack::initializeTransaction($config['paystack'], $payload);
        $data = $init['data'] ?? [];

        $upd = $pdo->prepare('UPDATE invoices SET paystack_access_code = ?, paystack_reference = ? WHERE id = ?');
        $upd->execute([$data['access_code'] ?? null, $reference, $invoiceId]);

        Http::json([
            'authorization_url' => $data['authorization_url'] ?? null,
            'access_code' => $data['access_code'] ?? null,
            'reference' => $reference,
        ]);
        exit;
    }

    if ($path === '/payments/manual' && $method === 'POST') {
        $body = Http::readJsonBody();
        $invoiceId = (int)($body['invoice_id'] ?? 0);
        $amount = (float)($body['amount'] ?? 0);

        if ($invoiceId <= 0 || $amount <= 0) {
            Http::json(['error' => 'invoice_id and amount are required'], 422);
            exit;
        }

        $invStmt = $pdo->prepare('SELECT id, learner_id, guardian_id, amount_due, amount_paid, status FROM invoices WHERE id = ?');
        $invStmt->execute([$invoiceId]);
        $inv = $invStmt->fetch();
        if (!$inv) {
            Http::json(['error' => 'Invoice not found'], 404);
            exit;
        }

        if ($inv['status'] === 'void') {
            Http::json(['error' => 'Invoice is void'], 409);
            exit;
        }

        $due = (float)$inv['amount_due'];
        $paid = (float)$inv['amount_paid'];
        $balance = max(0.0, $due - $paid);
        if ($balance <= 0.00001) {
            Http::json(['error' => 'Invoice already fully paid'], 409);
            exit;
        }
        if ($amount - $balance > 0.00001) {
            Http::json(['error' => 'Amount cannot exceed outstanding balance'], 422);
            exit;
        }

        $ins = $pdo->prepare(
            "INSERT INTO payments (invoice_id, learner_id, guardian_id, amount, currency, method, channel, paid_at, received_by, notes)
             VALUES (?, ?, ?, ?, ?, 'manual', ?, ?, ?, ?)"
        );
        $ins->execute([
            $invoiceId,
            (int)$inv['learner_id'],
            $inv['guardian_id'] !== null ? (int)$inv['guardian_id'] : null,
            $amount,
            $config['paystack']['currency'],
            $body['channel'] ?? 'cash',
            $body['paid_at'] ?? date('Y-m-d H:i:s'),
            $body['received_by'] ?? null,
            $body['notes'] ?? null,
        ]);

        $updated = Billing::applyPayment($pdo, $invoiceId, $amount);
        sendPaymentReceipt($pdo, $config, $invoiceId, $amount, 'manual', null);
        Http::json(['payment_id' => (int)$pdo->lastInsertId(), 'invoice' => $updated], 201);
        exit;
    }

    if ($path === '/course-sessions' && $method === 'GET') {
        $rows = $pdo->query(
            "SELECT cs.*, c.name AS course_name
             FROM course_sessions cs
             JOIN courses c ON c.id = cs.course_id
             ORDER BY cs.id DESC
             LIMIT 200"
        )->fetchAll();
        Http::json(['data' => $rows]);
        exit;
    }

    if ($path === '/course-sessions' && $method === 'POST') {
        $body = Http::readJsonBody();
        $courseName = trim((string)($body['course_name'] ?? ''));
        $courseDescription = $body['course_description'] ?? null;
        $courseStatus = (string)($body['course_status'] ?? 'active');
        $sessionName = trim((string)($body['session_name'] ?? ''));
        $monthlyRate = (float)($body['monthly_rate'] ?? 0);
        $billingDay = (int)($body['billing_day'] ?? 1);
        $currency = (string)($body['currency'] ?? $config['paystack']['currency']);

        if ($courseName === '' || $sessionName === '' || $monthlyRate <= 0) {
            Http::json(['error' => 'course_name, session_name, monthly_rate are required'], 422);
            exit;
        }

        if (!in_array($courseStatus, ['active', 'inactive'], true)) {
            Http::json(['error' => 'Invalid course_status'], 422);
            exit;
        }

        $billingDay = max(1, min(28, $billingDay));

        $pdo->beginTransaction();
        try {
            $courseStmt = $pdo->prepare('SELECT id FROM courses WHERE name = ? LIMIT 1');
            $courseStmt->execute([$courseName]);
            $courseId = (int)($courseStmt->fetchColumn() ?: 0);
            if ($courseId <= 0) {
                $insC = $pdo->prepare('INSERT INTO courses (name, description, default_monthly_rate, currency, status) VALUES (?, ?, ?, ?, ?)');
                $insC->execute([$courseName, $courseDescription, $monthlyRate, $currency, $courseStatus]);
                $courseId = (int)$pdo->lastInsertId();
            } else {
                $updC = $pdo->prepare('UPDATE courses SET description = COALESCE(?, description), default_monthly_rate = ?, currency = ?, status = ? WHERE id = ?');
                $updC->execute([$courseDescription, $monthlyRate, $currency, $courseStatus, $courseId]);
            }

            $insS = $pdo->prepare(
                'INSERT INTO course_sessions (course_id, session_name, start_date, end_date, monthly_rate, billing_day, status) VALUES (?, ?, ?, ?, ?, ?, ?)'
            );
            $insS->execute([
                $courseId,
                $sessionName,
                $body['start_date'] ?? null,
                $body['end_date'] ?? null,
                $monthlyRate,
                $billingDay,
                $body['status'] ?? 'active',
            ]);

            $pdo->commit();
            Http::json(['id' => (int)$pdo->lastInsertId()], 201);
            exit;
        } catch (Throwable $e) {
            $pdo->rollBack();
            throw $e;
        }
    }

    if ($path === '/subscriptions' && $method === 'GET') {
        $billingMonth = (string)($_GET['billing_month'] ?? date('Y-m'));
        if (!preg_match('/^\d{4}-\d{2}$/', $billingMonth)) {
            Http::json(['error' => 'Invalid billing_month'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            "SELECT s.*, 
                    l.learner_code, l.first_name, l.last_name,
                    cs.session_name, c.name AS course_name,
                    g.full_name AS guardian_name,
                    (
                        SELECT sd.discount_type
                        FROM subscription_discounts sd
                        WHERE sd.subscription_id = s.id
                          AND sd.status = 'active'
                          AND sd.start_month <= ?
                          AND (sd.end_month IS NULL OR sd.end_month = '' OR sd.end_month >= ?)
                        ORDER BY sd.start_month DESC, sd.id DESC
                        LIMIT 1
                    ) AS current_discount_type,
                    (
                        SELECT sd.discount_value
                        FROM subscription_discounts sd
                        WHERE sd.subscription_id = s.id
                          AND sd.status = 'active'
                          AND sd.start_month <= ?
                          AND (sd.end_month IS NULL OR sd.end_month = '' OR sd.end_month >= ?)
                        ORDER BY sd.start_month DESC, sd.id DESC
                        LIMIT 1
                    ) AS current_discount_value
             FROM subscriptions s
             JOIN learners l ON l.id = s.learner_id
             JOIN course_sessions cs ON cs.id = s.course_session_id
             JOIN courses c ON c.id = cs.course_id
             LEFT JOIN guardians g ON g.id = COALESCE(s.guardian_id, l.guardian_id)
             ORDER BY s.id DESC
             LIMIT 200"
        );
        $stmt->execute([$billingMonth, $billingMonth, $billingMonth, $billingMonth]);
        $rows = $stmt->fetchAll();

        Http::json(['data' => $rows, 'billing_month' => $billingMonth]);
        exit;
    }

    if ($path === '/subscriptions' && $method === 'POST') {
        $body = Http::readJsonBody();
        $learnerId = (int)($body['learner_id'] ?? 0);
        $courseSessionId = (int)($body['course_session_id'] ?? 0);
        $startDate = (string)($body['start_date'] ?? date('Y-m-d'));
        $status = (string)($body['status'] ?? 'active');

        if ($learnerId <= 0 || $courseSessionId <= 0) {
            Http::json(['error' => 'learner_id and course_session_id are required'], 422);
            exit;
        }

        if (!in_array($status, ['active', 'paused', 'ended'], true)) {
            Http::json(['error' => 'Invalid status'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            'INSERT INTO subscriptions (learner_id, guardian_id, course_session_id, start_date, end_date, monthly_rate_override, status) VALUES (?, ?, ?, ?, ?, ?, ?)'
        );
        $stmt->execute([
            $learnerId,
            isset($body['guardian_id']) ? (int)$body['guardian_id'] : null,
            $courseSessionId,
            $startDate,
            $body['end_date'] ?? null,
            $body['monthly_rate_override'] ?? null,
            $status,
        ]);

        Http::json(['id' => (int)$pdo->lastInsertId()], 201);
        exit;
    }

    if ($path === '/discounts' && $method === 'POST') {
        $body = Http::readJsonBody();
        $subscriptionId = (int)($body['subscription_id'] ?? 0);
        $type = (string)($body['discount_type'] ?? 'fixed');
        $value = (float)($body['discount_value'] ?? 0);
        $startMonth = (string)($body['start_month'] ?? '');
        $endMonth = $body['end_month'] ?? null;
        $status = (string)($body['status'] ?? 'active');

        if ($subscriptionId <= 0 || !in_array($type, ['fixed', 'percent'], true) || $value <= 0 || !preg_match('/^\d{4}-\d{2}$/', $startMonth)) {
            Http::json(['error' => 'subscription_id, discount_type, discount_value, start_month are required'], 422);
            exit;
        }
        if ($endMonth !== null && $endMonth !== '' && !preg_match('/^\d{4}-\d{2}$/', (string)$endMonth)) {
            Http::json(['error' => 'Invalid end_month'], 422);
            exit;
        }

        if (!in_array($status, ['active', 'inactive'], true)) {
            Http::json(['error' => 'Invalid status'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            'INSERT INTO subscription_discounts (subscription_id, discount_type, discount_value, start_month, end_month, reason, status) VALUES (?, ?, ?, ?, ?, ?, ?)'
        );
        $stmt->execute([
            $subscriptionId,
            $type,
            $value,
            $startMonth,
            $endMonth ?: null,
            $body['reason'] ?? null,
            $status,
        ]);

        Http::json(['id' => (int)$pdo->lastInsertId()], 201);
        exit;
    }

    if ($path === '/paystack/webhook' && $method === 'POST') {
        $raw = file_get_contents('php://input') ?: '';
        $sig = $_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] ?? '';

        $whSecret = (string)($config['paystack']['webhook_secret'] ?? '');
        if ($whSecret === '' || $whSecret === 'CHANGE_ME') {
            $whSecret = (string)($config['paystack']['secret_key'] ?? '');
        }

        if (!Paystack::verifyWebhookSignature($whSecret, $raw, $sig)) {
            Http::json(['error' => 'Invalid signature'], 401);
            exit;
        }

        $event = json_decode($raw, true);
        if (!is_array($event)) {
            Http::json(['error' => 'Invalid payload'], 400);
            exit;
        }

        $eventType = (string)($event['event'] ?? '');
        $data = $event['data'] ?? [];

        if ($eventType !== 'charge.success' || !is_array($data)) {
            Http::json(['ok' => true]);
            exit;
        }

        $reference = (string)($data['reference'] ?? '');
        $amount = isset($data['amount']) ? ((float)$data['amount'] / 100.0) : 0.0;
        $currency = (string)($data['currency'] ?? $config['paystack']['currency']);
        $paidAt = (string)($data['paid_at'] ?? '');
        $channel = (string)($data['channel'] ?? '');

        $meta = $data['metadata'] ?? [];
        $invoiceId = is_array($meta) ? (int)($meta['invoice_id'] ?? 0) : 0;

        if ($invoiceId <= 0 || $reference === '' || $amount <= 0) {
            Http::json(['ok' => true]);
            exit;
        }

        $invStmt = $pdo->prepare('SELECT id, learner_id, guardian_id FROM invoices WHERE id = ?');
        $invStmt->execute([$invoiceId]);
        $inv = $invStmt->fetch();
        if (!$inv) {
            Http::json(['ok' => true]);
            exit;
        }

        $exists = $pdo->prepare('SELECT id FROM payments WHERE paystack_reference = ? LIMIT 1');
        $exists->execute([$reference]);
        if ($exists->fetchColumn()) {
            Http::json(['ok' => true]);
            exit;
        }

        $paidAtDt = $paidAt !== '' ? date('Y-m-d H:i:s', strtotime($paidAt)) : date('Y-m-d H:i:s');

        $ins = $pdo->prepare(
            "INSERT INTO payments (invoice_id, learner_id, guardian_id, amount, currency, method, channel, paystack_reference, paid_at, raw_payload)
             VALUES (?, ?, ?, ?, ?, 'paystack', ?, ?, ?, ?)"
        );
        $ins->execute([
            $invoiceId,
            (int)$inv['learner_id'],
            $inv['guardian_id'] !== null ? (int)$inv['guardian_id'] : null,
            $amount,
            $currency,
            $channel,
            $reference,
            $paidAtDt,
            $raw,
        ]);

        $updated = Billing::applyPayment($pdo, $invoiceId, $amount);
        sendPaymentReceipt($pdo, $config, $invoiceId, $amount, 'paystack', $reference);
        Http::json(['ok' => true, 'invoice' => $updated]);
        exit;
    }

    if ($path === '/reports/owing' && $method === 'GET') {
        $month = $_GET['billing_month'] ?? date('Y-m');
        if (!preg_match('/^\\d{4}-\\d{2}$/', (string)$month)) {
            Http::json(['error' => 'Invalid billing_month'], 422);
            exit;
        }

        $stmt = $pdo->prepare(
            "SELECT i.id AS invoice_id, i.billing_month, i.due_date, i.amount_due, i.amount_paid,
                    (i.amount_due - i.amount_paid) AS balance,
                    l.id AS learner_id, l.learner_code, l.first_name, l.last_name,
                    g.id AS guardian_id, g.full_name AS guardian_name, g.phone AS guardian_phone, g.email AS guardian_email
             FROM invoices i
             JOIN learners l ON l.id = i.learner_id
             LEFT JOIN guardians g ON g.id = i.guardian_id
             WHERE i.billing_month = ?
               AND i.status IN ('unpaid','partial')
             ORDER BY balance DESC, i.due_date ASC"
        );
        $stmt->execute([(string)$month]);
        Http::json(['billing_month' => (string)$month, 'data' => $stmt->fetchAll()]);
        exit;
    }

    if ($path === '/notifications/send-owing' && $method === 'POST') {
        $body = Http::readJsonBody();
        $month = (string)($body['billing_month'] ?? date('Y-m'));
        $channel = (string)($body['channel'] ?? 'sms');
        $invoiceId = isset($body['invoice_id']) ? (int)$body['invoice_id'] : 0;

        if (!preg_match('/^\\d{4}-\\d{2}$/', $month)) {
            Http::json(['error' => 'Invalid billing_month'], 422);
            exit;
        }
        if (!in_array($channel, ['sms', 'email', 'both', 'auto'], true)) {
            Http::json(['error' => 'Invalid channel'], 422);
            exit;
        }

        $query =
            "SELECT i.id AS invoice_id, i.billing_month, i.due_date, i.amount_due, i.amount_paid,
                    (i.amount_due - i.amount_paid) AS balance,
                    l.id AS learner_id, l.first_name, l.last_name,
                    g.id AS guardian_id, g.full_name AS guardian_name, g.phone AS guardian_phone, g.email AS guardian_email
             FROM invoices i
             JOIN learners l ON l.id = i.learner_id
             LEFT JOIN guardians g ON g.id = i.guardian_id
             WHERE i.billing_month = ?
               AND i.status IN ('unpaid','partial')";

        $params = [$month];
        if ($invoiceId > 0) {
            $query .= ' AND i.id = ?';
            $params[] = $invoiceId;
        }

        $stmt = $pdo->prepare($query);
        $stmt->execute($params);
        $rows = $stmt->fetchAll();

        $sent = 0;
        $failed = 0;
        $items = [];

        foreach ($rows as $r) {
            $balance = (float)$r['balance'];
            if ($balance <= 0) {
                continue;
            }

            $learnerName = trim((string)$r['first_name'] . ' ' . (string)$r['last_name']);
            $amountStr = number_format($balance, 2);

            $recipient = '';
            $subject = null;
            $message = '';
            $provider = null;
            $providerMessageId = null;
            $status = 'failed';
            $error = null;

            $phone = trim((string)($r['guardian_phone'] ?? ''));
            $email = trim((string)($r['guardian_email'] ?? ''));
            $wantSms = in_array($channel, ['sms', 'both', 'auto'], true);
            $wantEmail = in_array($channel, ['email', 'both', 'auto'], true);

            $attempts = [];
            if ($channel === 'sms' && $phone === '' && $email !== '') {
                $wantSms = false;
                $wantEmail = true;
            }
            if ($channel === 'email' && $email === '' && $phone !== '') {
                $wantEmail = false;
                $wantSms = true;
            }

            if ($wantSms) {
                $attempts[] = 'sms';
            }
            if ($wantEmail) {
                $attempts[] = 'email';
            }
            if (empty($attempts)) {
                $attempts = [$channel];
            }

            $invoiceItemStatus = 'failed';
            $invoiceItemErrors = [];

            foreach ($attempts as $sendChannel) {
                $recipient = '';
                $subject = null;
                $message = '';
                $provider = null;
                $providerMessageId = null;
                $status = 'failed';
                $error = null;

                try {
                    if ($sendChannel === 'sms') {
                        $recipient = $phone;
                        if ($recipient === '') {
                            throw new RuntimeException('Guardian phone missing');
                        }
                        $message = "Superb Systems Academy: Payment reminder for {$learnerName}. Amount due: GHS {$amountStr}.";
                        $provider = 'mnotify';
                        $resp = Mnotify::sendSms($config['mnotify'], $recipient, $message);
                        $providerMessageId = is_string($resp['raw'] ?? null) ? (string)$resp['raw'] : null;
                    } else {
                        $recipient = $email;
                        if ($recipient === '') {
                            throw new RuntimeException('Guardian email missing');
                        }
                        $subject = 'Payment Reminder - Superb Systems Academy';
                        $message = "<p>Payment reminder for <strong>{$learnerName}</strong>.</p><p>Amount due: <strong>GHS {$amountStr}</strong></p>";
                        $provider = 'smtp';
                        Mailer::send($config['email'], $recipient, $subject, $message);
                    }

                    $status = 'sent';
                    $sent++;
                    $invoiceItemStatus = 'sent';
                } catch (Throwable $e) {
                    $error = $e->getMessage();
                    $failed++;
                    $invoiceItemErrors[] = "{$sendChannel}: {$error}";
                }

                $logStmt = $pdo->prepare(
                    'INSERT INTO notification_logs (invoice_id, learner_id, guardian_id, channel, recipient, template_key, subject, message, provider, provider_message_id, status, error_message, sent_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
                );
                $logStmt->execute([
                    (int)$r['invoice_id'],
                    $r['learner_id'] !== null ? (int)$r['learner_id'] : null,
                    $r['guardian_id'] !== null ? (int)$r['guardian_id'] : null,
                    $sendChannel,
                    $recipient,
                    'owing_reminder',
                    $subject,
                    $message,
                    $provider,
                    $providerMessageId,
                    $status,
                    $error,
                    $status === 'sent' ? date('Y-m-d H:i:s') : null,
                ]);
            }

            $items[] = [
                'invoice_id' => (int)$r['invoice_id'],
                'status' => $invoiceItemStatus,
                'error' => empty($invoiceItemErrors) ? null : implode(' | ', $invoiceItemErrors),
            ];
        }

        Http::json(['billing_month' => $month, 'channel' => $channel, 'sent' => $sent, 'failed' => $failed, 'items' => $items]);
        exit;
    }

    if ($path === '/notifications/send-paylink' && $method === 'POST') {
        $body = Http::readJsonBody();
        $month = (string)($body['billing_month'] ?? date('Y-m'));
        $channel = (string)($body['channel'] ?? 'sms');
        $invoiceIds = $body['invoice_ids'] ?? [];

        if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
            Http::json(['error' => 'Invalid billing_month'], 422);
            exit;
        }
        if (!in_array($channel, ['sms', 'email'], true)) {
            Http::json(['error' => 'Invalid channel'], 422);
            exit;
        }
        if (!is_array($invoiceIds)) {
            $invoiceIds = [];
        }

        $baseQuery =
            "SELECT i.id AS invoice_id, i.billing_month, i.due_date, i.amount_due, i.amount_paid,
                    (i.amount_due - i.amount_paid) AS balance,
                    l.id AS learner_id, l.first_name, l.last_name, l.email AS learner_email,
                    g.id AS guardian_id, g.full_name AS guardian_name, g.phone AS guardian_phone, g.email AS guardian_email
             FROM invoices i
             JOIN learners l ON l.id = i.learner_id
             LEFT JOIN guardians g ON g.id = i.guardian_id
             WHERE i.billing_month = ?
               AND i.status IN ('unpaid','partial')";
        $params = [$month];

        if (count($invoiceIds) > 0) {
            $placeholders = implode(',', array_fill(0, count($invoiceIds), '?'));
            $baseQuery .= " AND i.id IN ($placeholders)";
            foreach ($invoiceIds as $id) {
                $params[] = (int)$id;
            }
        }

        $stmt = $pdo->prepare($baseQuery);
        $stmt->execute($params);
        $rows = $stmt->fetchAll();

        $sent = 0;
        $failed = 0;
        $items = [];

        foreach ($rows as $r) {
            $balance = (float)$r['balance'];
            if ($balance <= 0) {
                continue;
            }

            $invoiceId = (int)$r['invoice_id'];
            $learnerName = trim((string)$r['first_name'] . ' ' . (string)$r['last_name']);
            $amountStr = number_format($balance, 2);

            $recipient = '';
            $subject = null;
            $message = '';
            $provider = null;
            $providerMessageId = null;
            $status = 'failed';
            $error = null;

            try {
                $emailForPaystack = (string)($r['guardian_email'] ?: $r['learner_email']);
                if (trim($emailForPaystack) === '') {
                    throw new RuntimeException('No email available for Paystack');
                }

                $reference = 'SSA-' . $invoiceId . '-' . bin2hex(random_bytes(6));
                $amountKobo = (int) round($balance * 100);

                $payload = [
                    'email' => $emailForPaystack,
                    'amount' => $amountKobo,
                    'currency' => $config['paystack']['currency'],
                    'reference' => $reference,
                    'callback_url' => $config['paystack']['callback_url'],
                    'metadata' => [
                        'invoice_id' => $invoiceId,
                        'learner_id' => (int)$r['learner_id'],
                        'billing_month' => (string)$r['billing_month'],
                        'learner_name' => $learnerName,
                    ],
                ];

                $init = Paystack::initializeTransaction($config['paystack'], $payload);
                $data = $init['data'] ?? [];
                $authUrl = (string)($data['authorization_url'] ?? '');
                if (trim($authUrl) === '') {
                    throw new RuntimeException('Paystack authorization_url missing');
                }

                $upd = $pdo->prepare('UPDATE invoices SET paystack_access_code = ?, paystack_reference = ? WHERE id = ?');
                $upd->execute([$data['access_code'] ?? null, $reference, $invoiceId]);

                if ($channel === 'sms') {
                    $recipient = (string)($r['guardian_phone'] ?? '');
                    if (trim($recipient) === '') {
                        throw new RuntimeException('Guardian phone missing');
                    }
                    $message = "Superb Systems Academy: Pay for {$learnerName} (GHS {$amountStr}) using: {$authUrl}";
                    $provider = 'mnotify';
                    $resp = Mnotify::sendSms($config['mnotify'], $recipient, $message);
                    $providerMessageId = is_string($resp['raw'] ?? null) ? (string)$resp['raw'] : null;
                } else {
                    $recipient = (string)($r['guardian_email'] ?: $r['learner_email']);
                    if (trim($recipient) === '') {
                        throw new RuntimeException('Email missing');
                    }
                    $subject = 'Payment Link - Superb Systems Academy';
                    $message = "<p>Hello,</p><p>Please pay for <strong>{$learnerName}</strong> for <strong>{$month}</strong>.</p><p>Amount: <strong>GHS {$amountStr}</strong></p><p><a href=\"{$authUrl}\">Click here to pay</a></p>";
                    $provider = 'smtp';
                    Mailer::send($config['email'], $recipient, $subject, $message);
                }

                $status = 'sent';
                $sent++;
            } catch (Throwable $e) {
                $error = $e->getMessage();
                $failed++;
            }

            $logStmt = $pdo->prepare(
                'INSERT INTO notification_logs (invoice_id, learner_id, guardian_id, channel, recipient, template_key, subject, message, provider, provider_message_id, status, error_message, sent_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
            );
            $logStmt->execute([
                $invoiceId,
                $r['learner_id'] !== null ? (int)$r['learner_id'] : null,
                $r['guardian_id'] !== null ? (int)$r['guardian_id'] : null,
                $channel,
                $recipient,
                'paylink',
                $subject,
                $message,
                $provider,
                $providerMessageId,
                $status,
                $error,
                $status === 'sent' ? date('Y-m-d H:i:s') : null,
            ]);

            $items[] = ['invoice_id' => $invoiceId, 'status' => $status, 'error' => $error];
        }

        Http::json(['billing_month' => $month, 'channel' => $channel, 'sent' => $sent, 'failed' => $failed, 'items' => $items]);
        exit;
    }

    Http::json(['error' => 'Not found'], 404);
} catch (Throwable $e) {
    Http::json(['error' => $e->getMessage()], 500);
}
