<?php

require_once __DIR__ . '/env.php';
require_once __DIR__ . '/database.php';
require_once __DIR__ . '/account_data.php';

if (!function_exists('conventa_smtp_read')) {
    function conventa_smtp_read($socket): string
    {
        $response = '';
        while (($line = fgets($socket, 515)) !== false) {
            $response .= $line;
            if (strlen($line) >= 4 && $line[3] === ' ') {
                break;
            }
        }

        return $response;
    }
}

if (!function_exists('conventa_smtp_command')) {
    function conventa_smtp_command($socket, string $command, array $expectedCodes): string
    {
        fwrite($socket, $command . "\r\n");
        $response = conventa_smtp_read($socket);
        $code = (int) substr($response, 0, 3);
        if (!in_array($code, $expectedCodes, true)) {
            throw new RuntimeException('SMTP command failed: ' . trim($response));
        }

        return $response;
    }
}

if (!function_exists('conventa_smtp_format_address')) {
    function conventa_smtp_format_address(string $email, string $name = ''): string
    {
        $email = trim($email);
        $name = trim($name);
        if ($name === '') {
            return '<' . $email . '>';
        }

        $safeName = addcslashes($name, "\\\"");
        return '"' . $safeName . '" <' . $email . '>';
    }
}

if (!function_exists('conventa_smtp_send')) {
    function conventa_smtp_send(string $to, string $subject, string $body): bool
    {
        $host = conventa_env('CONVENTA_SMTP_HOST', '');
        $port = (int) conventa_env('CONVENTA_SMTP_PORT', '465');
        $secure = strtolower((string) conventa_env('CONVENTA_SMTP_SECURE', 'ssl'));
        $username = conventa_env('CONVENTA_SMTP_USERNAME', '');
        $password = conventa_env('CONVENTA_SMTP_PASSWORD', '');
        $fromEmail = conventa_env('CONVENTA_SMTP_FROM_EMAIL', $username ?: 'no-reply@dcsmanagement.au');
        $fromName = conventa_env('CONVENTA_SMTP_FROM_NAME', 'Conventa');
        $replyTo = conventa_env('CONVENTA_SMTP_REPLY_TO', $fromEmail);

        if (!$host || !$port || !$username || !$password || !$fromEmail) {
            return false;
        }

        $remote = ($secure === 'ssl' ? 'ssl://' : '') . $host . ':' . $port;
        $socket = @stream_socket_client($remote, $errno, $errstr, 20, STREAM_CLIENT_CONNECT);
        if (!$socket) {
            return false;
        }

        stream_set_timeout($socket, 20);

        try {
            $greeting = conventa_smtp_read($socket);
            if ((int) substr($greeting, 0, 3) !== 220) {
                throw new RuntimeException('SMTP greeting failed: ' . trim($greeting));
            }

            $serverName = $_SERVER['HTTP_HOST'] ?? 'localhost';
            conventa_smtp_command($socket, 'EHLO ' . $serverName, [250]);

            if ($secure === 'tls') {
                conventa_smtp_command($socket, 'STARTTLS', [220]);
                if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                    throw new RuntimeException('SMTP STARTTLS failed.');
                }
                conventa_smtp_command($socket, 'EHLO ' . $serverName, [250]);
            }

            conventa_smtp_command($socket, 'AUTH LOGIN', [334]);
            conventa_smtp_command($socket, base64_encode($username), [334]);
            conventa_smtp_command($socket, base64_encode($password), [235]);

            conventa_smtp_command($socket, 'MAIL FROM:<' . $fromEmail . '>', [250]);
            conventa_smtp_command($socket, 'RCPT TO:<' . $to . '>', [250, 251]);
            conventa_smtp_command($socket, 'DATA', [354]);

            $headers = [
                'Date: ' . date('r'),
                'From: ' . conventa_smtp_format_address($fromEmail, $fromName),
                'Reply-To: ' . conventa_smtp_format_address((string) $replyTo),
                'To: ' . conventa_smtp_format_address($to),
                'Subject: ' . str_replace(["\r", "\n"], '', $subject),
                'MIME-Version: 1.0',
                'Content-Type: text/plain; charset=UTF-8',
                'Content-Transfer-Encoding: 8bit',
            ];

            $message = implode("\r\n", $headers) . "\r\n\r\n" . $body;
            $message = preg_replace('/^\./m', '..', $message) ?? $message;
            fwrite($socket, $message . "\r\n.\r\n");
            $response = conventa_smtp_read($socket);
            if ((int) substr($response, 0, 3) !== 250) {
                throw new RuntimeException('SMTP DATA failed: ' . trim($response));
            }

            conventa_smtp_command($socket, 'QUIT', [221]);
            fclose($socket);
            return true;
        } catch (Throwable $e) {
            @fclose($socket);
            error_log('Conventa SMTP mail failed: ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('conventa_password_reset_init')) {
    function conventa_password_reset_init(PDO $db): bool
    {
        $sql = "
            CREATE TABLE IF NOT EXISTS password_resets (
                id INT AUTO_INCREMENT PRIMARY KEY,
                adminuser_id INT NOT NULL,
                token_hash CHAR(64) NOT NULL,
                expires_at DATETIME NOT NULL,
                used_at DATETIME NULL,
                created_at DATETIME NOT NULL,
                INDEX idx_password_resets_token_hash (token_hash),
                INDEX idx_password_resets_adminuser_id (adminuser_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ";

        return $db->exec($sql) !== false;
    }
}

if (!function_exists('conventa_password_reset_base_url')) {
    function conventa_password_reset_base_url(): string
    {
        $configuredBaseUrl = rtrim((string) conventa_env('CONVENTA_BASE_URL', ''), '/');
        if ($configuredBaseUrl !== '') {
            return $configuredBaseUrl;
        }

        $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
        $path = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '')), '/');

        return $scheme . '://' . $host . ($path === '' ? '' : $path);
    }
}

if (!function_exists('conventa_password_reset_create')) {
    function conventa_password_reset_create(PDO $db, string $username): array
    {
        $username = trim($username);
        if ($username === '') {
            return ['ok' => false, 'message' => 'Please enter your username/email.'];
        }

        if (!conventa_password_reset_init($db)) {
            return ['ok' => false, 'message' => 'Password reset is not available right now.'];
        }

        $user = conventa_admin_user_find_by_username($db, $username);

        $genericMessage = 'If that username exists, a password reset link has been sent.';
        if (!$user) {
            return ['ok' => true, 'message' => $genericMessage];
        }

        $token = bin2hex(random_bytes(32));
        $tokenHash = hash('sha256', $token);
        $adminUserId = (int) $user['id'];
        $expiresAt = (new DateTime('+1 hour'))->format('Y-m-d H:i:s');
        $createdAt = (new DateTime())->format('Y-m-d H:i:s');

        try {
            $cleanup = $db->prepare('UPDATE password_resets SET used_at = ? WHERE adminuser_id = ? AND used_at IS NULL');
            $cleanup->execute([$createdAt, $adminUserId]);

            $insert = $db->prepare('INSERT INTO password_resets (adminuser_id, token_hash, expires_at, created_at) VALUES (?, ?, ?, ?)');
            $insert->execute([$adminUserId, $tokenHash, $expiresAt, $createdAt]);
        } catch (Throwable $e) {
            return ['ok' => false, 'message' => 'Password reset is not available right now.'];
        }

        $resetUrl = conventa_password_reset_base_url() . '/reset-password.php?token=' . urlencode($token);
        $to = (string) $user['username'];
        $name = trim((string) ($user['name'] ?? ''));
        $greeting = $name !== '' ? 'Hi ' . $name . ',' : 'Hi,';
        $subject = 'Conventa password reset';
        $body = $greeting . "\r\n\r\n"
            . "A password reset was requested for your Conventa account.\r\n\r\n"
            . "Reset your password using this link:\r\n"
            . $resetUrl . "\r\n\r\n"
            . "This link expires in 1 hour. If you did not request this reset, you can ignore this email.\r\n";
        if (!conventa_smtp_send($to, $subject, $body)) {
            return ['ok' => false, 'message' => 'Password reset email could not be sent. Please check SMTP configuration.'];
        }

        return ['ok' => true, 'message' => $genericMessage];
    }
}

if (!function_exists('conventa_password_reset_find')) {
    function conventa_password_reset_find(PDO $db, string $token): ?array
    {
        if (!conventa_password_reset_init($db)) {
            return null;
        }

        $token = trim($token);
        if ($token === '' || !preg_match('/^[a-f0-9]{64}$/i', $token)) {
            return null;
        }

        $tokenHash = hash('sha256', $token);
        try {
            $stmt = $db->prepare(
                'SELECT pr.id, pr.adminuser_id, pr.expires_at, pr.used_at, au.username
                 FROM password_resets pr
                 JOIN adminusers au ON au.id = pr.adminuser_id
                 WHERE pr.token_hash = ?
                 LIMIT 1'
            );
            $stmt->execute([$tokenHash]);
            $reset = $stmt->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            return null;
        }

        if (!$reset || !empty($reset['used_at'])) {
            return null;
        }

        $expiresAt = DateTime::createFromFormat('Y-m-d H:i:s', (string) $reset['expires_at']);
        if (!$expiresAt || $expiresAt < new DateTime()) {
            return null;
        }

        return $reset;
    }
}

if (!function_exists('conventa_password_reset_apply')) {
    function conventa_password_reset_apply(PDO $db, string $token, string $password): bool
    {
        $reset = conventa_password_reset_find($db, $token);
        if (!$reset) {
            return false;
        }

        $passwordHash = password_hash($password, PASSWORD_DEFAULT);
        $adminUserId = (int) $reset['adminuser_id'];
        $usedAt = (new DateTime())->format('Y-m-d H:i:s');

        try {
            $db->beginTransaction();

            $updateUser = $db->prepare('UPDATE adminusers SET password = ? WHERE id = ?');
            $userOk = $updateUser->execute([$passwordHash, $adminUserId]);

            $resetId = (int) $reset['id'];
            $updateToken = $db->prepare('UPDATE password_resets SET used_at = ? WHERE id = ?');
            $tokenOk = $updateToken->execute([$usedAt, $resetId]);

            if (!$userOk || !$tokenOk) {
                $db->rollBack();
                return false;
            }

            $db->commit();
            return true;
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            return false;
        }
    }
}
