/**
 * src/onboarding/routes.ts
 * مسارات معالج الإعداد — Onboarding wizard API
 * Updated: Supports "Other" industry + Custom Persona + Graphiti Hive Mind
 */
import { z } from 'zod';
import { createChildLogger } from '../utils/logger.js';
import { createTenant, getTenantByOwnerId } from '../tenants/manager.js';
import { writeWorkspaceFile, readWorkspaceFile, listWorkspaceFiles } from '../tenants/workspace.js';
import { getTenantWorkspacePath } from '../tenants/workspace.js';
import { writeFile as fsWriteFile, mkdir as fsMkdir } from 'node:fs/promises';
import { join as joinPath, dirname as pathDirname } from 'node:path';
import { applyIndustryTemplate, listIndustries } from './industries.js';
import { authMiddleware, tenantMiddleware } from '../auth/middleware.js';
import { initTenantGraph, indexInitialDocuments } from '../memory/graphiti.js';
const log = createChildLogger('onboarding:routes');
// ─── Schemas ─────────────────────────────────────────────────────────
const startOnboardingSchema = z.object({
    business_name: z.string().min(1).max(200),
    business_name_ar: z.string().min(1).max(200),
    agent_name: z.string().min(1).max(100).default('مساعد'),
    industry: z.string().min(1).max(50),
    city: z.string().min(1).max(100),
    language: z.enum(['ar', 'en', 'both']).default('both'),
    whatsapp_phone: z.string().regex(/^\+\d{10,15}$/, 'Invalid phone format'),
    // Custom fields for "Other" industry
    custom_role: z.string().max(500).optional(),
    custom_specialty: z.string().max(1000).optional(),
    custom_tone: z.string().max(500).optional(),
    custom_description: z.string().max(2000).optional(),
    custom_products: z.string().max(5000).optional(),
    custom_processes: z.string().max(5000).optional(),
});
// ─── Route Registration ──────────────────────────────────────────────
export async function registerOnboardingRoutes(app) {
    // ─── GET /api/onboarding/industries — قائمة القطاعات المتاحة ───
    app.get('/api/onboarding/industries', async (_request, reply) => {
        return reply.send({
            success: true,
            data: listIndustries(),
        });
    });
    // ─── POST /api/onboarding/start — بدء الإعداد وإنشاء المستأجر ───
    app.post('/api/onboarding/start', { preHandler: [authMiddleware] }, async (request, reply) => {
        try {
            const body = startOnboardingSchema.parse(request.body);
            const user = request.user;
            log.info({ userId: user.id, business: body.business_name, industry: body.industry }, 'Starting onboarding');
            // Idempotency: if this owner already provisioned a tenant (e.g. the
            // previous /start succeeded server-side but the client's fetch
            // dropped), return that tenant instead of building a duplicate.
            // Without this guard the user would have to manually delete state
            // before they could retry — and meanwhile the workspace gets
            // re-provisioned which wastes minutes of LLM time.
            const existing = await getTenantByOwnerId(user.id);
            if (existing) {
                log.info({ tenantId: existing.id, ownerId: user.id }, 'Onboarding idempotent return — owner already has tenant');
                return reply.status(200).send({
                    success: true,
                    data: {
                        tenant_id: existing.id,
                        status: 'ready',
                        business_name: existing.business_name,
                        agent_name: existing.agent_name,
                        industry: existing.settings?.industry ?? body.industry,
                        plan: existing.plan,
                        next_steps: ['upload_documents', 'customize_persona', 'send_test_message'],
                        idempotent: true,
                    },
                });
            }
            // إنشاء المستأجر — owner_id مرتبط مباشرة بـ auth.users.id
            const tenant = await createTenant({
                business_name: body.business_name,
                business_name_ar: body.business_name_ar,
                agent_name: body.agent_name,
                whatsapp_phone: body.whatsapp_phone,
                owner_id: user.id,
                owner_name: user.user_metadata?.name || user.phone || 'Owner',
                owner_phone: user.phone,
                owner_email: user.email,
                plan: 'trial',
                settings: {
                    industry: body.industry,
                    city: body.city,
                    language: body.language,
                },
            });
            // تطبيق قالب القطاع (مع دعم "آخر" للتخصيص الكامل)
            const customData = body.industry === 'other' ? {
                role: body.custom_role,
                specialty: body.custom_specialty,
                tone: body.custom_tone,
                businessDescription: body.custom_description,
                products: body.custom_products,
                processes: body.custom_processes,
            } : undefined;
            await applyIndustryTemplate(tenant.id, body.industry, customData);
            // تهيئة قاعدة المعرفة الموحدة (Graphiti) — الـ "Hive Mind"
            await initTenantGraph(tenant.id);
            // فهرسة المستندات الأولية في الرسم البياني
            const soulContent = await readWorkspaceFile(tenant.id, 'SOUL.md');
            const productsContent = await readWorkspaceFile(tenant.id, 'PRODUCTS.md');
            const processesContent = await readWorkspaceFile(tenant.id, 'PROCESSES.md');
            if (soulContent && productsContent && processesContent) {
                await indexInitialDocuments(tenant.id, {
                    soul: soulContent,
                    products: productsContent,
                    processes: processesContent,
                });
            }
            // Seed the knowledge graph with the bundled skill/policy/cron/tool
            // catalog. Before this call the KG was empty after onboarding and
            // the dashboard showed 0 skills / 0 entities — every dashboard
            // metric is read from kg_entities, so without the seed the UI is
            // a wasteland on first launch. (Equivalent of `moasis reseed-kg`
            // but auto-triggered instead of requiring a CLI step.)
            try {
                const { seedAllToKnowledgeGraph } = await import('../autonomy/kg-seeder.js');
                const seedResult = await seedAllToKnowledgeGraph(tenant.id);
                log.info({ tenantId: tenant.id, ...seedResult }, 'KG seeded with bundled skills/policies/tools');
            }
            catch (err) {
                log.warn({ error: err.message, tenantId: tenant.id }, 'KG auto-seed failed (will need manual `moasis reseed-kg`)');
            }
            // تحديث معلومات العمل بالمدينة
            const businessMd = await readWorkspaceFile(tenant.id, 'BUSINESS.md');
            if (businessMd) {
                const updated = businessMd.replace('<!-- أضف العنوان هنا -->', `${body.city}، المملكة العربية السعودية`);
                await writeWorkspaceFile(tenant.id, 'BUSINESS.md', updated);
            }
            log.info({ tenantId: tenant.id, business: body.business_name }, 'Onboarding started ✅');
            return reply.status(201).send({
                success: true,
                data: {
                    tenant_id: tenant.id,
                    status: 'ready',
                    business_name: tenant.business_name,
                    agent_name: tenant.agent_name,
                    industry: body.industry,
                    plan: 'trial',
                    next_steps: [
                        'upload_documents',
                        'customize_persona',
                        'send_test_message',
                    ],
                },
            });
        }
        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,
                    })),
                });
            }
            const message = err instanceof Error ? err.message : String(err);
            log.error({ error: message }, 'Onboarding start failed');
            if (message.includes('already registered')) {
                return reply.status(409).send({ success: false, error: message });
            }
            return reply.status(500).send({ success: false, error: 'Internal server error' });
        }
    });
    // ─── POST /api/onboarding/:tenantId/upload — رفع المستندات ───
    app.post('/api/onboarding/:tenantId/upload', { preHandler: [authMiddleware, tenantMiddleware] }, async (request, reply) => {
        try {
            const { tenantId } = request.params;
            const file = await request.file();
            if (!file) {
                return reply.status(400).send({ success: false, error: 'No file uploaded — لم يتم رفع ملف' });
            }
            // Permissive allowlist — accept anything the owner attaches via the
            // dashboard composer. The agent decides what to do with it; the
            // route just persists raw bytes. Blocking by mimetype caused PNG
            // screenshots / docx / audio to 400 even though the agent could
            // read them via tools. Only reject obvious executables to keep
            // workspace KNOWLEDGE/ from becoming a binary dump.
            const mt = (file.mimetype || '').toLowerCase();
            const isBlocked = /(application\/x-msdownload|application\/x-mach-binary|application\/x-executable|application\/x-sh)/.test(mt);
            if (isBlocked) {
                return reply.status(400).send({
                    success: false,
                    error: `نوع الملف محظور لأسباب أمنية: ${file.mimetype}`,
                });
            }
            // قراءة محتوى الملف
            const chunks = [];
            for await (const chunk of file.file) {
                chunks.push(chunk);
            }
            const content = Buffer.concat(chunks);
            // حفظ في مساحة العمل
            const filename = file.filename || 'uploaded_document';
            const safeName = filename.replace(/[^a-zA-Z0-9._\-\u0600-\u06FF]/g, '_');
            const docPath = `KNOWLEDGE/${safeName}`;
            // Write the raw Buffer, not a utf-8 string. The previous code
            // round-tripped binary content through utf-8 which silently
            // corrupted PDFs, images, and xlsx files — the agent then read
            // garbage when invoking read_file. Direct fs.writeFile with a
            // Buffer preserves bytes verbatim.
            const absolutePath = joinPath(getTenantWorkspacePath(tenantId), docPath);
            await fsMkdir(pathDirname(absolutePath), { recursive: true });
            await fsWriteFile(absolutePath, content);
            log.info({ tenantId, filename: safeName, size: content.length, absolutePath }, 'Document uploaded');
            // TODO: enable RAG ingestion in Graphiti
            // await graphiti.indexDocument(tenantId, docPath, content);
            return reply.send({
                success: true,
                data: {
                    filename: safeName,
                    path: docPath,
                    absolute_path: absolutePath,
                    size: content.length,
                    message: 'تم رفع المستند بنجاح — Document uploaded',
                },
            });
        }
        catch (err) {
            log.error({ error: err instanceof Error ? err.message : String(err) }, 'Upload failed');
            return reply.status(500).send({ success: false, error: 'Internal server error' });
        }
    });
    // ─── GET /api/onboarding/:tenantId/status — حالة الإعداد ───
    app.get('/api/onboarding/:tenantId/status', { preHandler: [authMiddleware, tenantMiddleware] }, async (request, reply) => {
        try {
            const { tenantId } = request.params;
            const tenant = request.tenant;
            // فحص اكتمال الخطوات
            const [soulMd, productsMd, processesMd, files] = await Promise.all([
                readWorkspaceFile(tenantId, 'SOUL.md'),
                readWorkspaceFile(tenantId, 'PRODUCTS.md'),
                readWorkspaceFile(tenantId, 'PROCESSES.md'),
                listWorkspaceFiles(tenantId, 'KNOWLEDGE'),
            ]);
            const steps = {
                tenant_created: true,
                workspace_ready: !!soulMd,
                persona_configured: soulMd ? !soulMd.includes('<!-- ') : false,
                products_added: productsMd ? !productsMd.includes('<!-- أضف') : false,
                processes_defined: processesMd ? !processesMd.includes('<!-- ') : false,
                documents_uploaded: files.length > 0,
                whatsapp_connected: !!tenant.whatsapp_phone,
            };
            const completedCount = Object.values(steps).filter(Boolean).length;
            const totalSteps = Object.keys(steps).length;
            const completionPercent = Math.round((completedCount / totalSteps) * 100);
            return reply.send({
                success: true,
                data: {
                    tenant_id: tenantId,
                    business_name: tenant.business_name,
                    plan: tenant.plan,
                    status: tenant.status,
                    onboarding: {
                        steps,
                        completed: completedCount,
                        total: totalSteps,
                        percent: completionPercent,
                        is_complete: completionPercent === 100,
                    },
                },
            });
        }
        catch (err) {
            log.error({ error: err instanceof Error ? err.message : String(err) }, 'Status check failed');
            return reply.status(500).send({ success: false, error: 'Internal server error' });
        }
    });
    // ─── POST /api/onboarding/:tenantId/generate-skills — توليد مهارات مخصصة ───
    // Re-trigger LLM skill generation after onboarding (e.g., the merchant
    // refined their business description and wants new skills).
    app.post('/api/onboarding/:tenantId/generate-skills', {
        preHandler: [authMiddleware, tenantMiddleware],
        // Generation is expensive (one big LLM call) — protect it.
        config: { rateLimit: { max: 5, timeWindow: '1 hour' } },
    }, async (request, reply) => {
        try {
            const { tenantId } = request.params;
            const body = z
                .object({
                businessDescription: z.string().min(30).max(4000),
                agentRole: z.string().max(500).optional(),
                agentTone: z.string().max(500).optional(),
            })
                .parse(request.body);
            const { generateAndPersistCustomSkills } = await import('./custom-skills.js');
            const skills = await generateAndPersistCustomSkills({
                tenantId,
                businessDescription: body.businessDescription,
                agentRole: body.agentRole,
                agentTone: body.agentTone,
            });
            return reply.send({
                success: true,
                data: {
                    count: skills.length,
                    skills: skills.map(s => ({ id: s.id, filename: s.filename })),
                },
            });
        }
        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) }, 'Custom skill generation failed');
            return reply.status(500).send({
                success: false,
                error: 'Skill generation failed',
                details: err instanceof Error ? err.message : String(err),
            });
        }
    });
    log.info('Onboarding routes registered ✅');
}
//# sourceMappingURL=routes.js.map