import type { KnowledgeModule, KnowledgeModuleWithCompanyFlag } from '@wa-ticketing/shared';
import type postgres from 'postgres';
import { sql } from '../db.js';

function slugify(raw: string): string {
  return raw
    .trim()
    .toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^a-z0-9-]/g, '')
    .slice(0, 120);
}

export const knowledgeModuleService = {
  async list(): Promise<KnowledgeModule[]> {
    return sql<KnowledgeModule[]>`
      SELECT id, slug, title, group_label, body_markdown, sort_order, created_at, updated_at
      FROM knowledge_modules
      ORDER BY sort_order ASC, title ASC
    `;
  },

  async getById(id: string): Promise<KnowledgeModule | null> {
    const [row] = await sql<[KnowledgeModule]>`
      SELECT id, slug, title, group_label, body_markdown, sort_order, created_at, updated_at
      FROM knowledge_modules WHERE id = ${id}
    `;
    return row ?? null;
  },

  async create(opts: {
    slug?: string;
    title: string;
    group_label?: string | null;
    body_markdown?: string;
    sort_order?: number;
  }): Promise<KnowledgeModule> {
    const title = opts.title.trim();
    if (!title) throw Object.assign(new Error('title is required'), { statusCode: 400 });
    const slug = (opts.slug?.trim() && slugify(opts.slug)) || slugify(title);
    if (!slug) throw Object.assign(new Error('slug is required'), { statusCode: 400 });
    const [row] = await sql<[KnowledgeModule]>`
      INSERT INTO knowledge_modules (slug, title, group_label, body_markdown, sort_order)
      VALUES (
        ${slug},
        ${title},
        ${opts.group_label?.trim() || null},
        ${opts.body_markdown ?? ''},
        ${opts.sort_order ?? 0}
      )
      RETURNING id, slug, title, group_label, body_markdown, sort_order, created_at, updated_at
    `;
    return row;
  },

  async update(
    id: string,
    opts: Partial<{
      slug: string;
      title: string;
      group_label: string | null;
      body_markdown: string;
      sort_order: number;
    }>,
  ): Promise<KnowledgeModule | null> {
    const cur = await this.getById(id);
    if (!cur) return null;
    const slug = opts.slug !== undefined ? slugify(opts.slug) : cur.slug;
    if (!slug) throw Object.assign(new Error('invalid slug'), { statusCode: 400 });
    const title = opts.title !== undefined ? opts.title.trim() : cur.title;
    if (!title) throw Object.assign(new Error('title is required'), { statusCode: 400 });
    const group_label =
      opts.group_label !== undefined ? (opts.group_label?.trim() || null) : cur.group_label;
    const body_markdown = opts.body_markdown !== undefined ? opts.body_markdown : cur.body_markdown;
    const sort_order = opts.sort_order !== undefined ? opts.sort_order : cur.sort_order;

    const [row] = await sql<[KnowledgeModule]>`
      UPDATE knowledge_modules
      SET
        slug = ${slug},
        title = ${title},
        group_label = ${group_label},
        body_markdown = ${body_markdown},
        sort_order = ${sort_order},
        updated_at = NOW()
      WHERE id = ${id}
      RETURNING id, slug, title, group_label, body_markdown, sort_order, created_at, updated_at
    `;
    return row ?? null;
  },

  async delete(id: string): Promise<boolean> {
    const rows = await sql<{ id: string }[]>`
      DELETE FROM knowledge_modules WHERE id = ${id} RETURNING id
    `;
    return rows.length > 0;
  },

  /** All modules with whether each is enabled for this company */
  async listForCompany(companyId: string): Promise<KnowledgeModuleWithCompanyFlag[]> {
    return sql<KnowledgeModuleWithCompanyFlag[]>`
      SELECT
        m.id,
        m.slug,
        m.title,
        m.group_label,
        m.body_markdown,
        m.sort_order,
        m.created_at,
        m.updated_at,
        (ck.module_id IS NOT NULL) AS enabled_for_company
      FROM knowledge_modules m
      LEFT JOIN company_knowledge_modules ck
        ON ck.module_id = m.id AND ck.company_id = ${companyId}
      ORDER BY m.sort_order ASC, m.title ASC
    `;
  },

  /** Replace enabled set (full list of module ids) */
  async setCompanyModules(companyId: string, moduleIds: string[]): Promise<void> {
    const uniq = [...new Set(moduleIds)];
    await sql.begin(async txn => {
      const q = txn as unknown as postgres.Sql;
      await q`DELETE FROM company_knowledge_modules WHERE company_id = ${companyId}`;
      for (const mid of uniq) {
        await q`
          INSERT INTO company_knowledge_modules (company_id, module_id)
          VALUES (${companyId}, ${mid})
        `;
      }
    });
  },

  /**
   * Enabled modules for AI: ordered, concatenated with H2 headings.
   * Returns null if nothing to inject (caller may still use channel KB only — talkback decides).
   */
  async buildCompanyKnowledgeMarkdown(companyId: string): Promise<{ text: string; titles: string[] } | null> {
    const rows = await sql<Array<{ title: string; body_markdown: string }>>`
      SELECT m.title, m.body_markdown
      FROM company_knowledge_modules ck
      INNER JOIN knowledge_modules m ON m.id = ck.module_id
      WHERE ck.company_id = ${companyId}
      ORDER BY m.sort_order ASC, m.title ASC
    `;
    if (!rows.length) return null;
    const titles = rows.map(r => r.title);
    const parts = rows.map(r => `## ${r.title}\n\n${r.body_markdown.trim()}`);
    return { text: parts.join('\n\n---\n\n'), titles };
  },

  /**
   * Like buildCompanyKnowledgeMarkdown but only modules whose ids are in restrictToModuleIds (intersection with company-enabled).
   * Use when an AI agent has a non-empty knowledge scope; empty selection means caller should use buildCompanyKnowledgeMarkdown instead.
   */
  async buildCompanyKnowledgeMarkdownScoped(
    companyId: string,
    restrictToModuleIds: string[],
  ): Promise<{ text: string; titles: string[] } | null> {
    const uniq = [...new Set(restrictToModuleIds.filter(Boolean))];
    if (!uniq.length) return null;
    const rows = await sql<Array<{ title: string; body_markdown: string }>>`
      SELECT m.title, m.body_markdown
      FROM company_knowledge_modules ck
      INNER JOIN knowledge_modules m ON m.id = ck.module_id
      WHERE ck.company_id = ${companyId}
        AND ck.module_id IN ${sql(uniq)}
      ORDER BY m.sort_order ASC, m.title ASC
    `;
    if (!rows.length) return null;
    const titles = rows.map(r => r.title);
    const parts = rows.map(r => `## ${r.title}\n\n${r.body_markdown.trim()}`);
    return { text: parts.join('\n\n---\n\n'), titles };
  },
};
