/**
 * ════════════════════════════════════════════════════════════════════
 * audit-assistant.service.ts
 * ────────────────────────────────────────────────────────────────────
 * AI agent over the audit data. Two tools:
 *   - query_audits  → counts + a list of matching rows (for "how many" / "show me")
 *   - export_audits → signals the frontend to download the FULL Excel (for "all")
 *
 * Engine: Groq (hosted, fast, free tier) when GROQ_API_KEY is set,
 *         otherwise local Ollama. Both use the OpenAI-style tools format.
 * ════════════════════════════════════════════════════════════════════
 */
import { Injectable, Logger } from '@nestjs/common';
import {
  AuditDetailReportService,
  type AuditDetailFilters,
} from './audit-detail-report.service';

// ── Ollama (local fallback) ──
const OLLAMA_URL = process.env.OLLAMA_URL || 'http://127.0.0.1:11434';
const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen2.5:3b';

// ── Groq (hosted, used when GROQ_API_KEY is present) ──
const GROQ_URL = 'https://api.groq.com/openai/v1/chat/completions';
const GROQ_MODEL = process.env.GROQ_MODEL || 'llama-3.3-70b-versatile';
const USE_GROQ = !!process.env.GROQ_API_KEY;

const filterProps = {
  source: { type: 'string', enum: ['QRS', 'TQS', ''], description: 'QRS, TQS, or empty for both' },
  auditor: { type: 'string', description: 'Auditor full name e.g. "Tahir Iqbal", or empty for all' },
  audit_type: { type: 'string', enum: ['Initial', 'Surveillance', 'Recertification', ''], description: 'Empty for all types' },
  report_status: { type: 'string', enum: ['s1_missing', 's2_missing', 's1_uploaded', 's2_uploaded', ''] },
  phase: { type: 'string', enum: ['conducted', 'upcoming', ''] },
  year: { type: 'string', description: 'Four-digit year e.g. "2026", or empty' },
  month: { type: 'string', description: 'A SINGLE month number 1-12 only, or empty. Never a range like "1-5".' },
  search: { type: 'string', description: 'Client name search, or empty' },
};

const tools = [
  {
    type: 'function',
    function: {
      name: 'query_audits',
      description:
        'Count or list audit records matching filters. Use for "how many", "which", ' +
        '"show me", or listing a manageable number of audits.',
      parameters: { type: 'object', properties: filterProps },
    },
  },
  {
    type: 'function',
    function: {
      name: 'export_audits',
      description:
        'Generate a downloadable Excel file of ALL audits matching the filters. ' +
        'Use this whenever the user asks to export, download, or get "all", "every", ' +
        '"the complete list", or "the full report". Do NOT use for counting.',
      parameters: {
        type: 'object',
        properties: { ...filterProps, format: { type: 'string', enum: ['excel', 'pdf'] } },
      },
    },
  },
];

type DownloadSignal = { format: 'excel' | 'pdf'; filters: AuditDetailFilters };

@Injectable()
export class AuditAssistantService {
  private readonly logger = new Logger('AuditAssistantService');

  constructor(private readonly detailReport: AuditDetailReportService) {}

  /** Returns the model-facing result, plus an optional download signal for the frontend. */
 private async runTool(
  name: string,
  input: any,
  ctx: { currentUserId: number; canViewAll: boolean },   // 🆕 add this 3rd param
): Promise<{ toolResult: any; download?: DownloadSignal }> {
  const clean: any = {};
  for (const [k, v] of Object.entries(input || {})) {
    if (v !== '' && v !== null && v !== undefined) clean[k] = v;
  }
  if (typeof clean.month === 'string' && /\D/.test(clean.month)) delete clean.month;

  // 🆕 force viewer scope, AFTER cleaning so the model can't override it
  if (!ctx.canViewAll) {
    clean.viewerUserId = ctx.currentUserId;
    delete clean.auditor;
  }
    if (name === 'query_audits') {
      const data = await this.detailReport.build({ ...clean, page: 1, limit: 1000 });   // ← input → clean
      return {
        toolResult: {
          total_matching: data.total,
          note: `EXACTLY ${data.total} audits match. Use this number for counts. Do not count the rows.`,
          summary: data.summary,
          returned_rows: data.rows.length,
          rows: data.rows.slice(0, 50).map((r) => ({
            client: r.client_name,
            auditor: r.auditor,
            date: r.audit_date,
            type: r.audit_type,
            source: r.source,
            stage1_uploaded: r.stage1_uploaded,
            stage2_uploaded: r.stage2_uploaded,
          })),
        },
      };
    }

    if (name === 'export_audits') {
      const { format, ...filters } = clean;   // ← was: input || {}
      const data = await this.detailReport.build({ ...filters, page: 1, limit: 1 });
      return {
        toolResult: {
          status: 'export_ready',
          total_rows: data.total,
          message: `Excel export prepared with ${data.total} audits. Tell the user the download is starting.`,
        },
        download: { format: format === 'pdf' ? 'pdf' : 'excel', filters },
      };
    }

    throw new Error(`Unknown tool: ${name}`);
  }

  /**
   * Calls the LLM. Returns a normalized { content, tool_calls } message so the
   * ask() loop doesn't care which engine produced it.
   */
  private async chat(messages: any[]): Promise<any> {
      const useGroq = !!process.env.GROQ_API_KEY;   // ← read at call time, not module load

   if (useGroq) {                              // ← was USE_GROQ, now useGroq
    const res = await fetch(GROQ_URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${process.env.GROQ_API_KEY}`,
        },
        body: JSON.stringify({
          model: GROQ_MODEL,
          messages,
          tools,
          tool_choice: 'auto',
          temperature: 0.2,
        }),
      });
      if (!res.ok) {
        const text = await res.text().catch(() => '');
        throw new Error(`Groq error ${res.status}: ${text}`);
      }
      const data = await res.json();
      return data.choices[0].message; // Groq → choices[0].message
    }

    // Ollama fallback
    const res = await fetch(`${OLLAMA_URL}/api/chat`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ model: OLLAMA_MODEL, messages, tools, stream: false }),
    });
    if (!res.ok) {
      const text = await res.text().catch(() => '');
      throw new Error(`Ollama error ${res.status}: ${text}`);
    }
    const data = await res.json();
    return data.message; // Ollama → data.message
  }

  async ask(
    message: string,
    history: { role: 'user' | 'assistant'; content: string }[] = [],
    ctx: { currentUserId: number; canViewAll: boolean },   // 🆕 CHANGE 1 — add this param
  ): Promise<{ answer: string; download?: DownloadSignal }> {
    const messages: any[] = [
      {
        role: 'system',
        content:
          'You answer questions about QRS/TQS audit data. Always use a tool — never guess numbers. ' +
          'For "how many" use query_audits and report the "total_matching" field exactly; never count rows yourself. ' +
          'For "export", "download", "all", or "complete list" requests, use export_audits. ' +
          'Never show internal field names (like stage1_missing) to the user. Answer in 1-2 short sentences.',
      },
      ...history,
      { role: 'user', content: message },
    ];

    let download: DownloadSignal | undefined;

    for (let i = 0; i < 6; i++) {
      const msg = await this.chat(messages);
      messages.push(msg);

      if (msg?.tool_calls?.length) {
        for (const call of msg.tool_calls) {
          // Groq sends arguments as a JSON string; Ollama sends an object. Handle both.
          let args: any = call.function.arguments;
          if (typeof args === 'string') {
            try { args = JSON.parse(args || '{}'); } catch { args = {}; }
          }
          try {
            const { toolResult, download: dl } = await this.runTool(call.function.name, args, ctx);  // 🆕 CHANGE 2 — add ctx
            if (dl) download = dl;
            // Groq needs tool_call_id; Ollama ignores it — safe to always include.
            messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(toolResult) });
          } catch (err: any) {
            this.logger.error(`Tool ${call.function?.name} failed: ${err.message}`);
            messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify({ error: err.message }) });
          }
        }
        continue;
      }

      return { answer: msg?.content ?? '', download };
    }
    return { answer: "I couldn't complete that — try a more specific question.", download };
  }
}