/**
 * src/auth/routes.ts
 * مسارات المصادقة — OTP عبر رقم الجوال السعودي
 */
import { z } from 'zod';
import { createChildLogger } from '../utils/logger.js';
import { config } from '../config.js';
import { supabaseAnon, supabase as supabaseAdmin } from '../utils/supabase.js';
import { authMiddleware, getDeviceOwnerOrNull } from './middleware.js';
const log = createChildLogger('auth:routes');
const DEVICE_BEARER = 'device';
const FAR_FUTURE_EXPIRY = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365 * 10;
// ─── Validation Schemas ──────────────────────────────────────────────
/** تنسيق رقم الجوال السعودي: +9665XXXXXXXX */
const saudiPhoneRegex = /^\+9665\d{8}$/;
const sendOtpSchema = z.object({
    phone: z.string().regex(saudiPhoneRegex, 'Invalid Saudi phone — يجب أن يبدأ بـ +9665 ثم 8 أرقام'),
});
const verifyOtpSchema = z.object({
    phone: z.string().regex(saudiPhoneRegex, 'Invalid Saudi phone format'),
    token: z.string().min(4).max(8, 'OTP must be 4-8 digits'),
});
const refreshSchema = z.object({
    refresh_token: z.string().min(1, 'Refresh token is required'),
});
// ─── Route Registration ──────────────────────────────────────────────
export async function registerAuthRoutes(app) {
    // ─── GET /api/auth/local-token — loopback-only token bootstrap ───
    // The dashboard's first action after load is to fetch this. We then verify
    // the request originates from the same machine (req.ip in the loopback
    // range). Any LAN attacker who reaches this endpoint via a misconfigured
    // proxy gets rejected. Once the dashboard has the token, every subsequent
    // call uses Bearer <token> like a real authed flow.
    app.get('/api/auth/local-token', async (request, reply) => {
        if (config.MUASSIS_MODE !== 'device') {
            return reply.status(404).send({ success: false, error: 'Not in device mode' });
        }
        const ip = request.ip;
        // (Audit L5) Comprehensive loopback check — IPv4 127.0.0.0/8, IPv6 ::1,
        // and IPv4-mapped-IPv6 forms. The previous fixed list missed valid
        // representations like ::ffff:127.0.0.5.
        const isLoopback = (() => {
            if (!ip)
                return false;
            if (ip === 'localhost')
                return true;
            if (ip === '::1')
                return true;
            // IPv4 127.0.0.0/8
            if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ip))
                return true;
            // IPv4-mapped IPv6 (::ffff:127.x.y.z)
            if (/^::ffff:127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/i.test(ip))
                return true;
            // IPv4-compat IPv6 (::127.x.y.z)
            if (/^::127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/i.test(ip))
                return true;
            return false;
        })();
        if (!isLoopback) {
            log.warn({ ip }, 'local-token requested from non-loopback — rejected');
            return reply.status(403).send({ success: false, error: 'local-token only available on loopback' });
        }
        try {
            const { existsSync, readFileSync } = await import('node:fs');
            const { join } = await import('node:path');
            const { getDataDir } = await import('../utils/data-dir.js');
            const tokenFile = join(getDataDir(), 'owner.token');
            if (!existsSync(tokenFile)) {
                return reply.status(409).send({
                    success: false,
                    error: 'owner.token missing — restart the gateway with `muassis start` to generate one',
                });
            }
            const token = readFileSync(tokenFile, 'utf-8').trim();
            return reply.send({ success: true, data: { token } });
        }
        catch (err) {
            return reply.status(500).send({ success: false, error: err.message });
        }
    });
    // ─── POST /api/auth/device-session — Phase 1 device-mode "auth" ───
    // No OTP, no SMS, no JWT. The device owner is whoever is at the keyboard.
    // Returns a sentinel bearer token the dashboard can keep using.
    app.post('/api/auth/device-session', async (_request, reply) => {
        if (config.MUASSIS_MODE !== 'device') {
            return reply.status(404).send({ success: false, error: 'Not in device mode' });
        }
        const user = getDeviceOwnerOrNull();
        if (!user) {
            return reply.status(409).send({
                success: false,
                error: 'Device not initialized — شغّل "muassis init" من الطرفية أولاً',
            });
        }
        return reply.send({
            success: true,
            data: {
                access_token: DEVICE_BEARER,
                refresh_token: DEVICE_BEARER,
                expires_at: FAR_FUTURE_EXPIRY,
                user: {
                    id: user.id,
                    phone: null,
                    created_at: user.created_at,
                    device_owner: true,
                },
            },
        });
    });
    // ─── POST /api/auth/otp/send — إرسال رمز التحقق ───
    // SMS abuse protection: 3/hour/IP — Supabase OTP triggers a paid SMS.
    app.post('/api/auth/otp/send', {
        config: { rateLimit: { max: 3, timeWindow: '1 hour' } },
    }, async (request, reply) => {
        try {
            // In device mode, OTP is a no-op: the owner is already on the device.
            // Tell the dashboard to skip straight to the verify step (which also
            // becomes a no-op and returns the device session).
            if (config.MUASSIS_MODE === 'device') {
                return reply.send({
                    success: true,
                    message: 'Device mode — no OTP needed. POST /api/auth/device-session to get a session.',
                    device_mode: true,
                });
            }
            const { phone } = sendOtpSchema.parse(request.body);
            log.info({ phone: phone.slice(0, 7) + '****' }, 'Sending OTP');
            const { error } = await supabaseAnon.auth.signInWithOtp({ phone });
            if (error) {
                log.error({ error: error.message }, 'Failed to send OTP');
                return reply.status(400).send({
                    success: false,
                    error: 'فشل إرسال رمز التحقق — حاول مرة أخرى',
                    details: error.message,
                });
            }
            return reply.send({
                success: true,
                message: 'تم إرسال رمز التحقق — OTP sent successfully',
            });
        }
        catch (err) {
            if (err instanceof z.ZodError) {
                return reply.status(400).send({
                    success: false,
                    error: 'Validation error',
                    details: err.issues.map(i => ({ field: i.path.join('.'), message: i.message })),
                });
            }
            log.error({ error: err instanceof Error ? err.message : String(err) }, 'OTP send error');
            return reply.status(500).send({ success: false, error: 'Internal server error' });
        }
    });
    // ─── POST /api/auth/otp/verify — التحقق من الرمز ───
    // Brute-force protection on OTP: 10/min/IP.
    app.post('/api/auth/otp/verify', {
        config: { rateLimit: { max: 10, timeWindow: '1 minute' } },
    }, async (request, reply) => {
        try {
            // Device mode: any verify call gets the same synthetic session. We
            // don't even validate the token; the OS-level fact that you're on
            // this device is the auth.
            if (config.MUASSIS_MODE === 'device') {
                const user = getDeviceOwnerOrNull();
                if (!user) {
                    return reply.status(409).send({
                        success: false,
                        error: 'Device not initialized — run "muassis init" first',
                    });
                }
                return reply.send({
                    success: true,
                    data: {
                        access_token: DEVICE_BEARER,
                        refresh_token: DEVICE_BEARER,
                        expires_at: FAR_FUTURE_EXPIRY,
                        user: { id: user.id, phone: null, created_at: user.created_at, device_owner: true },
                    },
                });
            }
            const { phone, token } = verifyOtpSchema.parse(request.body);
            log.info({ phone: phone.slice(0, 7) + '****' }, 'Verifying OTP');
            const { data, error } = await supabaseAnon.auth.verifyOtp({
                phone,
                token,
                type: 'sms',
            });
            if (error || !data.session) {
                log.warn({ error: error?.message }, 'OTP verification failed');
                return reply.status(401).send({
                    success: false,
                    error: 'رمز التحقق غير صحيح أو منتهي — Invalid or expired OTP',
                });
            }
            return reply.send({
                success: true,
                data: {
                    access_token: data.session.access_token,
                    refresh_token: data.session.refresh_token,
                    expires_at: data.session.expires_at,
                    user: {
                        id: data.user?.id,
                        phone: data.user?.phone,
                        created_at: data.user?.created_at,
                    },
                },
            });
        }
        catch (err) {
            if (err instanceof z.ZodError) {
                return reply.status(400).send({
                    success: false,
                    error: 'Validation error',
                    details: err.issues.map(i => ({ field: i.path.join('.'), message: i.message })),
                });
            }
            log.error({ error: err instanceof Error ? err.message : String(err) }, 'OTP verify error');
            return reply.status(500).send({ success: false, error: 'Internal server error' });
        }
    });
    // ─── POST /api/auth/refresh — تجديد التوكن ───
    app.post('/api/auth/refresh', async (request, reply) => {
        try {
            const { refresh_token } = refreshSchema.parse(request.body);
            const { data, error } = await supabaseAnon.auth.refreshSession({
                refresh_token,
            });
            if (error || !data.session) {
                return reply.status(401).send({
                    success: false,
                    error: 'فشل تجديد الجلسة — Invalid refresh token',
                });
            }
            return reply.send({
                success: true,
                data: {
                    access_token: data.session.access_token,
                    refresh_token: data.session.refresh_token,
                    expires_at: data.session.expires_at,
                },
            });
        }
        catch (err) {
            if (err instanceof z.ZodError) {
                return reply.status(400).send({
                    success: false,
                    error: 'Validation error',
                    details: err.issues.map(i => ({ field: i.path.join('.'), message: i.message })),
                });
            }
            log.error({ error: err instanceof Error ? err.message : String(err) }, 'Token refresh error');
            return reply.status(500).send({ success: false, error: 'Internal server error' });
        }
    });
    // ─── GET /api/auth/me — بيانات المستخدم الحالي ───
    app.get('/api/auth/me', { preHandler: [authMiddleware] }, async (request, reply) => {
        const user = request.user;
        return reply.send({
            success: true,
            data: {
                id: user.id,
                phone: user.phone,
                email: user.email,
                created_at: user.created_at,
                last_sign_in_at: user.last_sign_in_at,
                user_metadata: user.user_metadata,
            },
        });
    });
    // ─── POST /api/auth/logout — تسجيل الخروج ───
    app.post('/api/auth/logout', { preHandler: [authMiddleware] }, async (request, reply) => {
        // نستخدم service role client لإبطال جلسة المستخدم
        const user = request.user;
        if (user) {
            const { error } = await supabaseAdmin.auth.admin.signOut(user.id);
            if (error) {
                log.warn({ error: error.message }, 'Logout sign-out failed (non-critical)');
            }
        }
        return reply.send({
            success: true,
            message: 'تم تسجيل الخروج — Logged out successfully',
        });
    });
    log.info('Auth routes registered ✅');
}
//# sourceMappingURL=routes.js.map