/**
 * create-svc — Create (таблицы, дашборды), Export thread (PDF/MD)
 * docs/architecture: 01-perplexity-analogue-design.md §5.10
 * API: POST /api/v1/create (stub), POST /api/v1/export
 */

import Fastify from 'fastify';
import cors from '@fastify/cors';
import { z } from 'zod';
import { jsPDF } from 'jspdf';

const PORT = parseInt(process.env.PORT ?? '3011', 10);

const app = Fastify({ logger: true });

await app.register(cors, { origin: true });

const exportSectionSchema = z.object({
  query: z.string(),
  createdAt: z.union([z.string(), z.date()]),
  parsedTextBlocks: z.array(z.string()).optional().default([]),
  responseBlocks: z
    .array(
      z.object({
        type: z.string(),
        data: z.any().optional(),
      })
    )
    .optional()
    .default([]),
});

const exportBodySchema = z.object({
  format: z.enum(['pdf', 'md']),
  title: z.string().optional().default('chat'),
  sections: z.array(exportSectionSchema),
});

type ExportSection = z.infer<typeof exportSectionSchema>;

function buildMarkdown(title: string, sections: ExportSection[]): string {
  const date = new Date(
    (sections[0]?.createdAt as string) || Date.now()
  ).toLocaleString();
  let md = `# Chat Export: ${title}\n\n`;
  md += `*Exported on: ${date}*\n\n---\n`;

  sections.forEach((section) => {
    md += `\n---\n`;
    md += `**User**  \n`;
    md += `*${new Date(section.createdAt as string).toLocaleString()}*\n\n`;
    md += `> ${String(section.query).replace(/\n/g, '\n> ')}\n`;

    if (section.responseBlocks?.length) {
      const textBlock = section.responseBlocks.find((b) => b.type === 'text');
      const text = textBlock?.data ?? section.parsedTextBlocks?.join('\n') ?? '';
      if (text) {
        md += `\n**Assistant**  \n`;
        md += `*${new Date(section.createdAt as string).toLocaleString()}*\n\n`;
        md += `> ${text.replace(/\n/g, '\n> ')}\n`;
      }

      const sourceBlock = section.responseBlocks.find((b) => b.type === 'source');
      const sources = (sourceBlock?.data as { metadata?: { url?: string } }[]) ?? [];
      if (sources.length) {
        md += `\n**Citations:**\n`;
        sources.forEach((src: { metadata?: { url?: string } }, i: number) => {
          const url = src.metadata?.url ?? '';
          md += `- [${i + 1}] [${url}](${url})\n`;
        });
      }
    }
  });
  md += '\n---\n';
  return md;
}

function buildPDF(title: string, sections: ExportSection[]): Buffer {
  const doc = new jsPDF();
  const pageHeight = doc.internal.pageSize.height;
  let y = 15;

  const date = new Date(
    (sections[0]?.createdAt as string) || Date.now()
  ).toLocaleString();

  doc.setFontSize(18);
  doc.text(`Chat Export: ${title}`, 10, y);
  y += 8;
  doc.setFontSize(11);
  doc.setTextColor(100);
  doc.text(`Exported on: ${date}`, 10, y);
  y += 8;
  doc.setDrawColor(200);
  doc.line(10, y, 200, y);
  y += 6;
  doc.setTextColor(30);

  for (const section of sections) {
    if (y > pageHeight - 30) {
      doc.addPage();
      y = 15;
    }

    doc.setFont('helvetica', 'bold');
    doc.text('User', 10, y);
    doc.setFont('helvetica', 'normal');
    doc.setFontSize(10);
    doc.setTextColor(120);
    doc.text(
      `${new Date(section.createdAt as string).toLocaleString()}`,
      40,
      y
    );
    y += 6;
    doc.setTextColor(30);
    doc.setFontSize(12);
    const userLines = doc.splitTextToSize(String(section.query), 180);
    for (const line of userLines) {
      if (y > pageHeight - 20) {
        doc.addPage();
        y = 15;
      }
      doc.text(line, 12, y);
      y += 6;
    }
    y += 6;
    doc.setDrawColor(230);
    if (y > pageHeight - 10) {
      doc.addPage();
      y = 15;
    }
    doc.line(10, y, 200, y);
    y += 4;

    if (section.responseBlocks?.length) {
      const text =
        section.parsedTextBlocks?.join('\n') ??
        section.responseBlocks.find((b) => b.type === 'text')?.data ??
        '';
      if (text && y < pageHeight - 30) {
        doc.setFont('helvetica', 'bold');
        doc.text('Assistant', 10, y);
        doc.setFont('helvetica', 'normal');
        doc.setFontSize(10);
        doc.setTextColor(120);
        doc.text(
          `${new Date(section.createdAt as string).toLocaleString()}`,
          40,
          y
        );
        y += 6;
        doc.setTextColor(30);
        doc.setFontSize(12);
        const assistantLines = doc.splitTextToSize(String(text), 180);
        for (const line of assistantLines) {
          if (y > pageHeight - 20) {
            doc.addPage();
            y = 15;
          }
          doc.text(line, 12, y);
          y += 6;
        }

        const sourceBlock = section.responseBlocks.find(
          (b) => b.type === 'source'
        );
        const sources = (sourceBlock?.data as { metadata?: { url?: string } }[]) ?? [];
        if (sources.length && y < pageHeight - 20) {
          doc.setFontSize(11);
          doc.setTextColor(80);
          doc.text('Citations:', 12, y);
          y += 5;
          for (const src of sources) {
            const url = src.metadata?.url ?? '';
            if (y > pageHeight - 15) {
              doc.addPage();
              y = 15;
            }
            doc.text(`- ${url}`, 15, y);
            y += 5;
          }
          doc.setTextColor(30);
        }
        y += 6;
      }
      doc.setDrawColor(230);
      if (y > pageHeight - 10) {
        doc.addPage();
        y = 15;
      }
      doc.line(10, y, 200, y);
      y += 4;
    }
  }

  return Buffer.from(doc.output('arraybuffer'));
}

app.get('/health', async () => ({ status: 'ok' }));
app.get('/metrics', async (_req, reply) => {
  reply.header('Content-Type', 'text/plain; charset=utf-8');
  return reply.send('# HELP gooseek_up Service is up (1) or down (0)\n# TYPE gooseek_up gauge\ngooseek_up 1\n');
});
app.get('/ready', async () => ({ status: 'ready' }));

const createBodySchema = z.object({
  type: z.enum(['table', 'dashboard', 'image']),
  prompt: z.string().min(1),
  context: z.string().optional(),
});

app.post<{ Body: unknown }>('/api/v1/create', async (req, reply) => {
  const parsed = createBodySchema.safeParse(req.body);
  if (!parsed.success) {
    return reply.status(400).send({
      error: 'Invalid request body',
      issues: parsed.error.issues,
    });
  }
  const { type, prompt, context } = parsed.data;
  const apiKey = process.env.OPENAI_API_KEY ?? '';

  if (type === 'image') {
    if (!apiKey) {
      return reply.status(503).send({
        error: 'OPENAI_API_KEY not configured',
        message: 'Image generation requires OPENAI_API_KEY',
      });
    }
    try {
      const imgRes = await fetch('https://api.openai.com/v1/images/generations', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${apiKey}`,
        },
        body: JSON.stringify({
          model: 'dall-e-3',
          prompt: context ? `${prompt}\n\nContext:\n${context}` : prompt,
          n: 1,
          size: '1024x1024',
          quality: 'standard',
          style: 'vivid',
        }),
        signal: AbortSignal.timeout(120000),
      });
      if (!imgRes.ok) {
        const err = await imgRes.text();
        req.log.warn({ status: imgRes.status, err }, 'DALL·E error');
        return reply.status(502).send({
          error: 'Image generation failed',
          message: 'DALL·E API error',
        });
      }
      const imgData = (await imgRes.json()) as { data?: { url?: string; b64_json?: string }[] };
      const first = imgData.data?.[0];
      const url = first?.url ?? null;
      const b64 = first?.b64_json ?? null;
      return reply.send({
        type: 'image',
        data: null,
        url,
        b64: b64 ?? undefined,
        format: 'png',
      });
    } catch (err) {
      req.log.error(err);
      return reply.status(500).send({
        error: 'Image generation failed',
        message: err instanceof Error ? err.message : 'Unknown error',
      });
    }
  }

  if (!apiKey) {
    return reply.status(503).send({
      error: 'OPENAI_API_KEY not configured',
      message: 'Create requires OPENAI_API_KEY for table/dashboard generation',
    });
  }

  try {
    const sysPrompt =
      type === 'table'
        ? 'Generate a markdown table. Return ONLY valid markdown, no explanation. Use | for columns, --- for header separator.'
        : 'Generate dashboard data: a JSON object with title, description, and rows (array of objects). Return ONLY valid JSON.';
    const content = context ? `${prompt}\n\nContext:\n${context}` : prompt;

    const res = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: 'gpt-4o-mini',
        messages: [
          { role: 'system', content: sysPrompt },
          { role: 'user', content },
        ],
        max_tokens: 2000,
        temperature: 0.2,
      }),
      signal: AbortSignal.timeout(30000),
    });

    if (!res.ok) {
      const err = await res.text();
      req.log.warn({ status: res.status, err }, 'OpenAI create error');
      return reply.status(502).send({
        error: 'LLM request failed',
        message: 'Could not generate content',
      });
    }

    const data = (await res.json()) as { choices?: { message?: { content?: string } }[] };
    const raw = data.choices?.[0]?.message?.content?.trim() ?? '';

    if (type === 'table') {
      return reply.send({
        type: 'table',
        data: raw,
        url: null,
        format: 'markdown',
      });
    }

    let dashboard = raw;
    try {
      const jsonStart = raw.indexOf('{');
      if (jsonStart >= 0) {
        const jsonStr = raw.slice(jsonStart);
        const end = jsonStr.lastIndexOf('}') + 1;
        dashboard = jsonStr.slice(0, end);
      }
      JSON.parse(dashboard);
    } catch {
      dashboard = JSON.stringify({
        title: 'Dashboard',
        description: prompt,
        rows: [{ raw }],
      });
    }
    return reply.send({
      type: 'dashboard',
      data: dashboard,
      url: null,
    });
  } catch (err) {
    req.log.error(err);
    return reply.status(500).send({
      error: 'Create failed',
      message: err instanceof Error ? err.message : 'Unknown error',
    });
  }
});

app.post<{ Body: unknown }>('/api/v1/export', async (req, reply) => {
  const parsed = exportBodySchema.safeParse(req.body);
  if (!parsed.success) {
    return reply.status(400).send({
      error: 'Invalid request body',
      issues: parsed.error.issues,
    });
  }
  const { format, title, sections } = parsed.data;

  if (!sections.length) {
    return reply.status(400).send({ error: 'sections array is required and must not be empty' });
  }

  try {
    if (format === 'md') {
      const md = buildMarkdown(title, sections);
      return reply
        .header('Content-Type', 'text/markdown')
        .header('Content-Disposition', `attachment; filename="${title || 'chat'}.md"`)
        .send(md);
    }

    const pdfBuffer = buildPDF(title, sections);
    return reply
      .header('Content-Type', 'application/pdf')
      .header('Content-Disposition', `attachment; filename="${title || 'chat'}.pdf"`)
      .send(pdfBuffer);
  } catch (err) {
    req.log.error(err);
    return reply.status(500).send({ error: 'Export failed' });
  }
});

try {
  await app.listen({ port: PORT, host: '0.0.0.0' });
  console.log('create-svc listening on :' + PORT);
} catch (err) {
  console.error(err);
  process.exit(1);
}
