/**
 * AppointmentTypeService
 * ======================
 * Provides formatted catalog data for the WhatsApp AI booking engine.
 *
 * Responsibilities:
 *  - Fetch appointment types / service catalog from Prisma
 *  - Format category lists and service lists for WhatsApp messages
 *  - Provide bilingual (EN/AR) service summaries for AI system prompts
 *  - CSV import template generation (sector-aware)
 *  - Delegate Qatari dialect translation to QatariDialectService
 */

import { prisma } from '@/lib/db';
import { appointmentTypesAdapter } from '@/lib/adapters/appointment-types';
import { qatariDialectService } from '@/lib/services/qatari-dialect.service';
import { logger } from '@/lib/logger';
/** Appointment type record (replaces Prisma-generated type) */
interface appointment_types {
  id: string;
  userId: string;
  name: string;
  nameAr?: string | null;
  description?: string | null;
  descriptionAr?: string | null;
  duration: number;
  price?: number | null;
  currency?: string | null;
  category?: string | null;
  categoryAr?: string | null;
  color?: string | null;
  isActive: boolean;
  sortOrder?: number | null;
  createdAt: Date;
  updatedAt: Date;
  [key: string]: unknown;
}

// ─── Types ──────────────────────────────────────────────────────────────────

type Language = 'en' | 'ar';

interface CategoryRow {
  category: string;
  categoryAr: string | null;
  _count: { id: number };
}

// ─── Emoji number helpers ────────────────────────────────────────────────────

const EMOJI_NUMBERS = [
  '1️⃣',
  '2️⃣',
  '3️⃣',
  '4️⃣',
  '5️⃣',
  '6️⃣',
  '7️⃣',
  '8️⃣',
  '9️⃣',
  '🔟',
];

function numberPrefix(index: number): string {
  // 0-based index; emoji for 1–10, plain "N." for 11+
  if (index < EMOJI_NUMBERS.length) {
    return EMOJI_NUMBERS[index];
  }
  return `${index + 1}.`;
}

// ─── Service ─────────────────────────────────────────────────────────────────

export class AppointmentTypeService {
  // ── Public: formatted category list for WhatsApp ───────────────────────────

  /**
   * Returns a numbered (emoji) list of unique service categories for the org.
   *
   * English: "1️⃣ General Medicine\n2️⃣ Dental\n..."
   * Arabic:  "1️⃣ الطب العام\n2️⃣ طب الأسنان\n..."
   *
   * Falls back to empty string on error or when there are no categories.
   */
  static async getFormattedCategoriesList(
    organizationId: string,
    language: Language = 'en',
  ): Promise<string> {
    try {
      const rows = (await appointmentTypesAdapter.groupBy({
        by: ['category', 'categoryAr'],
        where: {
          organizationId,
          isActive: true,
          category: { not: null },
        },
        _count: { id: true },
        orderBy: { _count: { id: 'desc' } },
      })) as CategoryRow[];

      if (!rows || rows.length === 0) return '';

      const lines = rows.map((row, idx) => {
        const name =
          language === 'ar'
            ? (row.categoryAr ?? row.category)
            : row.category;
        const prefix = numberPrefix(idx);
        return `${prefix} ${name}`;
      });

      return lines.join('\n');
    } catch (error) {
      logger.error('[AppointmentTypeService] getFormattedCategoriesList failed', {
        error,
        organizationId,
      });
      return '';
    }
  }

  // ── Public: formatted services within a category ───────────────────────────

  /**
   * Returns a numbered list of services in the given category.
   *
   * English: "1. General Checkup (30 min - 150 QAR)\n2. Blood Test (15 min - 80 QAR)\n3. Vaccination (15 min)"
   * Arabic:  "1. فحص عام (30 دقيقة - 150 QAR)\n2. تحليل دم (15 دقيقة - 80 QAR)\n3. تطعيم (15 دقيقة)"
   *
   * Price is omitted when null/0.
   */
  static async getFormattedServicesInCategory(
    organizationId: string,
    category: string,
    language: Language = 'en',
  ): Promise<string> {
    try {
      const types = await AppointmentTypeService.getTypesInCategory(
        organizationId,
        category,
      );

      if (types.length === 0) return '';

      const minuteLabel = language === 'ar' ? 'دقيقة' : 'min';

      const lines = types.map((t, idx) => {
        const name = language === 'ar' ? t.nameAr : t.nameEn;
        const durationStr = `${t.duration} ${minuteLabel}`;

        let suffix: string;
        if (t.price != null && t.price > 0) {
          suffix = `(${durationStr} - ${t.price} QAR)`;
        } else {
          suffix = `(${durationStr})`;
        }

        return `${idx + 1}. ${name} ${suffix}`;
      });

      return lines.join('\n');
    } catch (error) {
      logger.error(
        '[AppointmentTypeService] getFormattedServicesInCategory failed',
        { error, organizationId, category },
      );
      return '';
    }
  }

  // ── Public: bilingual service list for AI system prompt ───────────────────

  /**
   * Returns a bilingual (EN / AR) service list suitable for injection into
   * the AI system prompt.
   *
   * Format per line: "- General Checkup / فحص عام (30 min, 150 QAR)"
   * Null price → "(30 min)"
   */
  static async getFormattedServicesList(
    organizationId: string,
  ): Promise<string> {
    try {
      const types = await appointmentTypesAdapter.findMany({
        where: { organizationId, isActive: true },
        orderBy: [{ category: 'asc' }, { sortOrder: 'asc' }],
        select: {
          nameEn: true,
          nameAr: true,
          duration: true,
          price: true,
        },
      });

      if (types.length === 0) return '';

      // Deduplicate by nameEn+duration to avoid showing 136 identical entries
      const seen = new Set<string>();
      const unique = types.filter((t) => {
        const key = `${t.nameEn}|${t.duration}`;
        if (seen.has(key)) return false;
        seen.add(key);
        return true;
      });

      const lines = unique.map((t) => {
        const svc = t as Record<string, unknown>;
        const price = svc.price as number | null;
        const pricePart = price != null && price > 0 ? `, ${price} QAR` : '';
        const arName = svc.nameAr ? ` / ${svc.nameAr}` : '';
        return `- ${svc.nameEn}${arName} (${svc.duration} min${pricePart})`;
      });

      return lines.join('\n');
    } catch (error) {
      logger.error('[AppointmentTypeService] getFormattedServicesList failed', {
        error,
        organizationId,
      });
      return '';
    }
  }

  // ── Public: simple name array ──────────────────────────────────────────────

  /**
   * Returns an array of service names in the requested language.
   */
  static async getTypeNames(
    organizationId: string,
    language: Language = 'en',
  ): Promise<string[]> {
    try {
      const types = await appointmentTypesAdapter.findMany({
        where: { organizationId, isActive: true },
        orderBy: { sortOrder: 'asc' },
        select: { nameEn: true, nameAr: true },
      });

      return types.map((t) => (language === 'ar' ? t.nameAr : t.nameEn));
    } catch (error) {
      logger.error('[AppointmentTypeService] getTypeNames failed', {
        error,
        organizationId,
      });
      return [];
    }
  }

  // ── Public: case-insensitive name lookup ───────────────────────────────────

  /**
   * Finds an appointment type by name (English or Arabic, case-insensitive).
   * Returns null when not found.
   */
  static async findTypeByName(
    organizationId: string,
    name: string,
  ): Promise<appointment_types | null> {
    try {
      const types = await appointmentTypesAdapter.findMany({
        where: { organizationId, isActive: true },
      });

      const needle = name.toLowerCase().trim();

      return (
        types.find(
          (t) =>
            t.nameEn.toLowerCase() === needle ||
            t.nameAr.toLowerCase() === needle,
        ) ?? null
      );
    } catch (error) {
      logger.error('[AppointmentTypeService] findTypeByName failed', {
        error,
        organizationId,
        name,
      });
      return null;
    }
  }

  // ── Public: CSV import template ────────────────────────────────────────────

  /**
   * Returns a CSV import template string.
   * The header row is always:
   *   nameEn,nameAr,duration,price,category,categoryAr,itemType
   *
   * Example rows are adapted to the sector (healthcare, restaurants, schools).
   * Defaults to healthcare if the sector is unknown.
   */
  static getTemplateCSV(sector?: string): string {
    const header = 'nameEn,nameAr,duration,price,category,categoryAr,itemType';

    const sectorNorm = (sector ?? '').toLowerCase();

    let rows: string[];

    if (
      sectorNorm.includes('restaurant') ||
      sectorNorm.includes('cafe') ||
      sectorNorm.includes('food') ||
      sectorNorm.includes('dining')
    ) {
      rows = [
        'Grilled Salmon,سلمون مشوي,,,Main Course,الطبق الرئيسي,menu_item',
        'Caesar Salad,سلطة سيزر,,,Starters,المقبلات,menu_item',
        'Chocolate Cake,كيك شوكولاتة,,,Desserts,الحلويات,menu_item',
      ];
    } else if (
      sectorNorm.includes('school') ||
      sectorNorm.includes('education') ||
      sectorNorm.includes('university') ||
      sectorNorm.includes('academy')
    ) {
      rows = [
        'Tuition Fee,رسوم الدراسة,,,Academic Fees,الرسوم الأكاديمية,fee',
        'Registration Fee,رسوم التسجيل,,,Administrative Fees,الرسوم الإدارية,fee',
        'Extracurricular Activities Fee,رسوم الأنشطة اللامنهجية,,,Activity Fees,رسوم الأنشطة,fee',
      ];
    } else {
      // Default: healthcare
      rows = [
        'Consultation,استشارة,30,150,General Medicine,الطب العام,service',
        'Follow-up Visit,زيارة المتابعة,20,80,General Medicine,الطب العام,service',
        'Blood Test,تحليل دم,15,60,Laboratory,المختبر,service',
      ];
    }

    return [header, ...rows].join('\n');
  }

  // ── Public: Qatari dialect translation ────────────────────────────────────

  /**
   * Translates English text to Qatari Arabic dialect.
   * Delegates to the shared QatariDialectService (Anthropic/cache-backed).
   */
  static async translateToQatari(text: string): Promise<string> {
    try {
      const result = await qatariDialectService.translateToQatari(text);
      return result.translation;
    } catch (error) {
      logger.error('[AppointmentTypeService] translateToQatari failed', {
        error,
        text: text.substring(0, 100),
      });
      return text; // fall back to original text
    }
  }

  // ── CRUD helpers (used by dashboard API routes) ────────────────────────────

  /**
   * Returns all appointment types for the organisation.
   * When activeOnly is true (default), only returns isActive=true rows.
   */
  static async getTypes(
    organizationId: string,
    activeOnly = true,
  ): Promise<appointment_types[]> {
    return appointmentTypesAdapter.findMany({
      where: { organizationId, ...(activeOnly ? { isActive: true } : {}) },
      orderBy: { sortOrder: 'asc' },
    });
  }

  /**
   * Creates a new appointment type for the organisation.
   */
  static async createType(
    organizationId: string,
    data: {
      nameEn: string;
      nameAr?: string;
      duration?: number;
      price?: number | null;
      category?: string | null;
      categoryAr?: string | null;
      createdBy?: string;
    },
  ): Promise<appointment_types> {
    const { createId } = await import('@paralleldrive/cuid2');
    const max = await appointmentTypesAdapter.aggregate({
      where: { organizationId },
      _max: { sortOrder: true },
    });
    const sortOrder = (max._max.sortOrder ?? -1) + 1;
    return appointmentTypesAdapter.create({
      data: {
        id: createId(),
        organizationId,
        nameEn: data.nameEn.trim(),
        nameAr: data.nameAr?.trim() ?? '',
        duration: data.duration ?? 30,
        price: data.price ?? null,
        category: data.category ?? null,
        categoryAr: data.categoryAr ?? null,
        sortOrder,
        createdBy: data.createdBy ?? null,
      },
    });
  }

  /**
   * Partially updates an existing appointment type.
   * Throws if the type is not found.
   */
  static async updateType(
    id: string,
    data: Partial<{
      nameEn: string;
      nameAr: string;
      duration: number;
      price: number | null;
      category: string | null;
      categoryAr: string | null;
      isActive: boolean;
    }>,
  ): Promise<appointment_types> {
    return appointmentTypesAdapter.update({ where: { id }, data });
  }

  /**
   * Soft-deletes an appointment type by setting isActive = false.
   */
  static async deactivateType(id: string): Promise<void> {
    await appointmentTypesAdapter.update({
      where: { id },
      data: { isActive: false },
    });
  }

  /**
   * Bulk-imports appointment types for an organisation.
   * Returns the count of successfully imported rows and any per-row errors.
   */
  static async importTypes(
    organizationId: string,
    rows: Array<{
      nameEn: string;
      nameAr?: string;
      duration?: number;
      price?: number | null;
      category?: string;
      categoryAr?: string;
    }>,
    createdBy?: string,
  ): Promise<{ imported: number; errors: string[] }> {
    const { createId } = await import('@paralleldrive/cuid2');
    const max = await appointmentTypesAdapter.aggregate({
      where: { organizationId },
      _max: { sortOrder: true },
    });
    let nextSortOrder = (max._max.sortOrder ?? -1) + 1;

    const errors: string[] = [];
    const validRows: Array<{
      id: string;
      organizationId: string;
      nameEn: string;
      nameAr: string;
      duration: number;
      price: number | null;
      category: string | null;
      categoryAr: string | null;
      sortOrder: number;
      createdBy: string | null;
    }> = [];

    rows.forEach((row, i) => {
      const label = `Row ${i + 1}`;
      if (!row.nameEn?.trim()) {
        errors.push(`${label}: nameEn is required`);
        return;
      }
      const price = row.price != null ? Number(row.price) : null;
      if (price !== null && (isNaN(price) || price < 0)) {
        errors.push(`${label}: price must be a non-negative number`);
        return;
      }
      validRows.push({
        id: createId(),
        organizationId,
        nameEn: row.nameEn.trim(),
        nameAr: row.nameAr?.trim() ?? '',
        duration: row.duration ? Number(row.duration) || 30 : 30,
        price,
        category: row.category ?? null,
        categoryAr: row.categoryAr ?? null,
        sortOrder: nextSortOrder++,
        createdBy: createdBy ?? null,
      });
    });

    if (validRows.length === 0) return { imported: 0, errors };
    const result = await appointmentTypesAdapter.createMany({
      data: validRows,
      skipDuplicates: true,
    });
    return { imported: result.count, errors };
  }

  /**
   * Updates sortOrder for multiple appointment types in a single transaction.
   */
  static async reorderTypes(
    items: Array<{ id: string; sortOrder: number }>,
  ): Promise<void> {
    await prisma.$transaction(
      items.map(({ id, sortOrder }) =>
        prisma.appointment_types.update({ where: { id }, data: { sortOrder } }),
      ),
    );
  }

  // ── Internal helpers (used by whatsapp-ai-engine) ──────────────────────────

  /**
   * Returns unique categories for the org (used for numbered selection in engine).
   * Ordered by count descending.
   */
  static async getCategoriesForOrg(
    organizationId: string,
  ): Promise<Array<{ category: string; categoryAr: string | null }>> {
    try {
      const rows = (await appointmentTypesAdapter.groupBy({
        by: ['category', 'categoryAr'],
        where: {
          organizationId,
          isActive: true,
          category: { not: null },
        },
        _count: { id: true },
        orderBy: { _count: { id: 'desc' } },
      })) as CategoryRow[];

      return rows.map((r) => ({
        category: r.category,
        categoryAr: r.categoryAr ?? null,
      }));
    } catch (error) {
      logger.error('[AppointmentTypeService] getCategoriesForOrg failed', {
        error,
        organizationId,
      });
      return [];
    }
  }

  /**
   * Returns all active appointment types within a given category, ordered by
   * sortOrder then nameEn.  Used for numbered service selection in engine.
   */
  static async getTypesInCategory(
    organizationId: string,
    category: string,
  ): Promise<appointment_types[]> {
    return appointmentTypesAdapter.findMany({
      where: {
        organizationId,
        category,
        isActive: true,
      },
      orderBy: [{ sortOrder: 'asc' }, { nameEn: 'asc' }],
    });
  }
}
