import type OpenAI from 'openai';
import type {
  ShapesMap,
  AttractionHydrated,
  EventHydrated,
} from '../shapes/shapes';

export type SectionTitle = { key: string; title: string };

// What AiService actually expects at runtime (grouped by collection)
export type GroupedItems = Partial<Record<keyof ShapesMap, unknown[]>> & {
  attraction?: AttractionHydrated[];
  event?: EventHydrated[];
};

export type ItemsByCollection = Partial<Record<keyof ShapesMap, unknown[]>>;

export type AiServiceDeps = {
  openai: OpenAI;
  chatModel: string;
  shapes: ShapesMap;
};

export class AiService {
  private openai: OpenAI;
  private chatModel: string;
  private shapes: ShapesMap;

  constructor({ openai, chatModel, shapes }: AiServiceDeps) {
    this.openai = openai;
    this.chatModel = chatModel;
    this.shapes = shapes;
  }

  safeJsonParse = <T>(s: string): T | null => {
    try {
      return JSON.parse(s) as T;
    } catch {
      return null;
    }
  };

  buildSectionTitles = async (
    query: string,
    itemsByCollection: ItemsByCollection,
  ): Promise<SectionTitle[]> => {
    const present = Object.keys(itemsByCollection).filter(
      (k) => (itemsByCollection[k as keyof ShapesMap]?.length ?? 0) > 0,
    );
    if (!present.length) return [];

    const defaults = Object.fromEntries(
      present.map((k) => [
        k,
        this.shapes[k as keyof ShapesMap]?.defaultTitle ??
          k.charAt(0).toUpperCase() + k.slice(1),
      ]),
    ) as Record<string, string>;

    const prompt = `
Act as a travel curator for DoSeeAfrica (tourism website).
Create short, inviting section titles (<=5 words) for a search results page based on the query and which collections are present.

Collections: ${present.join(', ')}
Query: ${query}

Respond with a pure JSON object whose keys are the collection names and values are titles. No commentary.
`.trim();

    try {
      const r = await this.openai.chat.completions.create({
        model: this.chatModel,
        messages: [{ role: 'user', content: prompt }],
        temperature: 0.3,
      });

      const text = r.choices?.[0]?.message?.content || '';
      const parsed = this.safeJsonParse<Record<string, string>>(text);

      return present.map((k) => ({
        key: k,
        title: (parsed && parsed[k]) || defaults[k],
      }));
    } catch {
      return present.map((k) => ({ key: k, title: defaults[k] }));
    }
  };

  synthesizeFallbackAnswer = (query: string, grouped: GroupedItems): string => {
    const parts: string[] = [];

    const hasAttractions = (grouped.attraction?.length ?? 0) > 0;
    const hasEvents = (grouped.event?.length ?? 0) > 0;

    if (hasEvents) {
      const evs = (grouped.event || [])
        .slice(0, 3)
        .map((e: any) => {
          const when = [e?.start_date, e?.start_time, e?.end_date, e?.end_time]
            .filter(Boolean)
            .join(' ');
          const where = [e?.venue, e?.formatted_address]
            .filter(Boolean)
            .join(', ');
          return `• ${e?.title ?? ''}${when ? ` — ${when}` : ''}${
            where ? ` — ${where}` : ''
          }`.trim();
        })
        .join('\n');
      parts.push(`Here are events that match your search:\n${evs}`);
    }

    if (hasAttractions) {
      const ats = (grouped.attraction || [])
        .slice(0, 3)
        .map((a: any) => {
          const where = [
            a.venue,
            a.formatted_address,
            a.city_level,
            a.country_short,
          ]
            .filter(Boolean)
            .join(', ');
          return `• ${a.title ?? ''}${where ? ` — ${where}` : ''}`.trim();
        })
        .join('\n');
      parts.push(`You might also like these attractions:\n${ats}`);
    }

    if (!parts.length) {
      return `I couldn’t find direct matches for "${query}", but I can help you explore nearby sights, festivals, and cultural experiences across East Africa. Try refining your search with a city, date, or theme (e.g., "jazz festival Kampala August", "museums in Nairobi").`;
    }

    return parts.join('\n\n');
  };

  generateAnswer = async (
    query: string,
    hits: unknown[],
    itemsByCollection: GroupedItems,
    context: string,
  ): Promise<string> => {
    if (!hits.length) {
      return `I couldn’t find direct matches for "${query}". Try a broader term (e.g., a city, landmark, or month), or explore popular attractions and upcoming events in your area.`;
    }

    const system = `
You are a warm, concise travel curator for DoSeeAfrica (a tourism website).
Ground every statement strictly in the provided context from our database (attractions, events, venues).
Never fabricate dates, times, locations, or prices. If a specific detail isn't in context, omit it.
Style: friendly, vivid, practical. Use short paragraphs and bullet points for highlights.
When helpful, reference items as [1], [2], ... matching the numbered context entries.
`.trim();

    const user = `
Query: ${query}

Context:
${context}

Write a short, informative answer tailored to travelers. If the query is about festivals/events, emphasize dates, venue, and location.
If it's about places, emphasize what it is and why it's worth visiting.
Include 3–6 bullet highlights if possible.

If context is too thin to answer fully, give your best guidance from what's present (do not say "I don't know" if there are items).
`.trim();

    try {
      const r = await this.openai.chat.completions.create({
        model: this.chatModel,
        messages: [
          { role: 'system', content: system },
          { role: 'user', content: user },
        ],
        temperature: 0.4,
      });

      const text = (r.choices?.[0]?.message?.content || '').trim();
      const looksEmpty = text.length < 50;
      const saysDontKnow = /i\s+don'?t\s+know/i.test(text);

      if (looksEmpty || saysDontKnow)
        return this.synthesizeFallbackAnswer(query, itemsByCollection);
      return text;
    } catch {
      return this.synthesizeFallbackAnswer(query, itemsByCollection);
    }
  };
}
