import type { Database } from 'bun:sqlite';
import { db } from '../db';
import { TaskService } from '../tasks/service';
import { ResponseQueue } from './response-queue';
import { ContactsService } from './contacts';
import { GroupSyncService } from './group-sync';
import { ICONS } from '../utils/icons';
import { codeId, formatDDMM, bold, italic } from '../utils/formatting';
import { AllowedGroups } from './allowed-groups';
import { Metrics } from './metrics';

type UserPreference = {
  user_id: string;
  reminder_freq: 'daily' | 'weekly' | 'weekdays' | 'off';
  reminder_time: string; // 'HH:MM'
  last_reminded_on: string | null; // 'YYYY-MM-DD'
};

export class RemindersService {
  static dbInstance: Database = db;

  private static _running = false;
  private static _timer: any = null;

  private static get TZ() {
    return process.env.TZ && process.env.TZ.trim() ? process.env.TZ : 'Europe/Madrid';
  }

  private static ymdInTZ(d: Date): string {
    const parts = new Intl.DateTimeFormat('en-GB', {
      timeZone: this.TZ,
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).formatToParts(d);
    const get = (t: string) => parts.find(p => p.type === t)?.value || '';
    return `${get('year')}-${get('month')}-${get('day')}`;
  }

  private static hmInTZ(d: Date): string {
    const parts = new Intl.DateTimeFormat('en-GB', {
      timeZone: this.TZ,
      hour: '2-digit',
      minute: '2-digit',
      hour12: false,
    }).formatToParts(d);
    const get = (t: string) => parts.find(p => p.type === t)?.value || '';
    return `${get('hour')}:${get('minute')}`;
  }

  private static weekdayShortInTZ(d: Date): string {
    return new Intl.DateTimeFormat('en-GB', {
      timeZone: this.TZ,
      weekday: 'short',
    }).format(d); // e.g., 'Mon', 'Tue', ...
  }

  static start() {
    if (process.env.NODE_ENV === 'test') return;
    if (this._running) return;
    this._running = true;

    // Arranca un tick cada minuto
    this._timer = setInterval(() => {
      this.runOnce().catch(err => {
        console.error('RemindersService runOnce error:', err);
      });
    }, 60_000);

    // Primer tick diferido para no bloquear el arranque
    setTimeout(() => this.runOnce().catch(() => {}), 5_000);
  }

  static stop() {
    this._running = false;
    if (this._timer) {
      clearInterval(this._timer);
      this._timer = null;
    }
  }

  static async runOnce(now: Date = new Date()): Promise<void> {
    const todayYMD = this.ymdInTZ(now);
    const nowHM = this.hmInTZ(now);
    const weekday = this.weekdayShortInTZ(now); // 'Mon'..'Sun'
    const graceRaw = Number(process.env.REMINDERS_GRACE_MINUTES);
    const GRACE_MIN = Number.isFinite(graceRaw) && graceRaw >= 0 ? Math.min(Math.floor(graceRaw), 180) : 60;

    const rows = this.dbInstance.prepare(`
      SELECT user_id, reminder_freq, reminder_time, last_reminded_on
      FROM user_preferences
      WHERE reminder_freq IN ('daily', 'weekly', 'weekdays')
    `).all() as UserPreference[];

    // Determinar si aplicar gating por grupos
    const enforce = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase() === 'enforce';
    if (enforce) {
      try {
        (AllowedGroups as any).dbInstance = this.dbInstance;
        // Evitar falsos positivos por caché obsoleta entre operaciones previas del test
        AllowedGroups.clearCache?.();
      } catch {}
    }

    for (const pref of rows) {
      // Evitar duplicado el mismo día
      if (pref.last_reminded_on === todayYMD) continue;

      // Verificar hora alcanzada y ventana de gracia
      if (!pref.reminder_time) continue;
      const [nowH, nowM] = String(nowHM).split(':');
      const [cfgH, cfgM] = String(pref.reminder_time).split(':');
      const nowMin = (parseInt(nowH || '0', 10) || 0) * 60 + (parseInt(nowM || '0', 10) || 0);
      const cfgMin = (parseInt(cfgH || '0', 10) || 0) * 60 + (parseInt(cfgM || '0', 10) || 0);

      // Antes de la hora programada
      if (nowMin < cfgMin) continue;

      // Sólo incrementar métrica si es un día válido para el usuario
      const isValidDay = !(
        (pref.reminder_freq === 'weekdays' && (weekday === 'Sat' || weekday === 'Sun')) ||
        (pref.reminder_freq === 'weekly' && weekday !== 'Mon')
      );

      // Fuera de ventana de gracia: saltar
      if (nowMin > cfgMin + GRACE_MIN) {
        try { if (isValidDay) Metrics.inc('reminders_skipped_outside_window_total'); } catch {}
        continue;
      }

      // Laborables: solo de lunes a viernes
      if (pref.reminder_freq === 'weekdays' && (weekday === 'Sat' || weekday === 'Sun')) continue;

      // Semanal: solo lunes (Mon)
      if (pref.reminder_freq === 'weekly' && weekday !== 'Mon') continue;

      try {
        // Obtener una lista amplia para filtrar correctamente por grupos permitidos
        const allPending = TaskService.listUserPending(pref.user_id, 1000);
        const filtered = enforce ? allPending.filter(t => !t.group_id || AllowedGroups.isAllowed(t.group_id)) : allPending;
        const total = filtered.length;
        const items = filtered.slice(0, 10);
        if (items.length === 0) {
          // No enviar si no hay tareas; no marcamos last_reminded_on para permitir enviar si aparecen más tarde hoy
          continue;
        }

        // Construir mensaje similar a "/t ver mis"
        const formatDDMM = (ymd?: string | null): string | null => {
          if (!ymd) return null;
          const parts = String(ymd).split('-');
          if (parts.length >= 3) {
            const [Y, M, D] = parts;
            if (D && M) return `${D}/${M}`;
          }
          return String(ymd);
        };

        const byGroup = new Map<string, typeof items>();
        for (const t of items) {
          const key = t.group_id || '(sin grupo)';
          const arr = byGroup.get(key) || [];
          arr.push(t);
          byGroup.set(key, arr);
        }

        const sections: string[] = [];
        sections.push(pref.reminder_freq === 'weekly' ? `${ICONS.reminder} RECORDATORIO SEMANAL — TUS TAREAS` : `${ICONS.reminder} RECORDATORIO DIARIO — TUS TAREAS`);

        for (const [groupId, arr] of byGroup.entries()) {
          const groupName =
            (groupId && GroupSyncService.activeGroupsCache.get(groupId)) ||
            (groupId && groupId !== '(sin grupo)' ? groupId : 'Sin grupo');

          sections.push(bold(groupName));
          const rendered = await Promise.all(arr.map(async (t) => {
            const names = await Promise.all(
              (t.assignees || []).map(async (uid) => (await ContactsService.getDisplayName(uid)) || uid)
            );
            const owner =
              (t.assignees?.length || 0) === 0
                ? `${ICONS.unassigned} sin responsable`
                : `${t.assignees!.length > 1 ? '👥' : '👤'} ${names.join(', ')}`;
            const isOverdue = t.due_date ? t.due_date < todayYMD : false;
            const datePart = t.due_date ? ` — ${isOverdue ? `${ICONS.warn} ` : ''}${ICONS.date} ${formatDDMM(t.due_date)}` : '';
            return `- ${codeId(t.id, t.display_code)} ${t.description || '(sin descripción)'}${datePart} — ${owner}`;
          }));
          sections.push(...rendered);
        }

        // Si hay más tareas de las listadas (tope), añadir resumen
        if (total > items.length) {
          sections.push(italic(`… y ${total - items.length} más`));
        }

        // (Etapa 3) Sección opcional de "sin responsable" filtrada por membresía activa + snapshot fresca.
        const includeUnassigned = String(process.env.REMINDERS_INCLUDE_UNASSIGNED_FROM_MEMBERSHIP || '').toLowerCase() === 'true';
        if (includeUnassigned) {
          let memberGroups = GroupSyncService.getFreshMemberGroupsForUser(pref.user_id);
          if (enforce) {
            memberGroups = memberGroups.filter(gid => AllowedGroups.isAllowed(gid));
          }
          for (const gid of memberGroups) {
            const unassigned = TaskService.listGroupUnassigned(gid, 10);
            if (unassigned.length > 0) {
              const groupName =
                (gid && GroupSyncService.activeGroupsCache.get(gid)) ||
                gid;
              sections.push(bold(`${groupName} — Sin responsable`));
              const renderedUnassigned = unassigned.map((t) => {
                const isOverdue = t.due_date ? t.due_date < todayYMD : false;
                const datePart = t.due_date ? ` — ${isOverdue ? `${ICONS.warn} ` : ''}${ICONS.date} ${formatDDMM(t.due_date)}` : '';
                return `- ${codeId(t.id, t.display_code)} ${t.description || '(sin descripción)'}${datePart} — ${ICONS.unassigned} sin responsable`;
              });
              sections.push(...renderedUnassigned);

              const totalUnassigned = TaskService.countGroupUnassigned(gid);
              if (totalUnassigned > unassigned.length) {
                sections.push(italic(`… y ${totalUnassigned - unassigned.length} más`));
              }
            }
          }
        }

        await ResponseQueue.add([{
          recipient: pref.user_id,
          message: sections.join('\n')
        }]);

        // Marcar como enviado hoy
        this.dbInstance.prepare(`
          INSERT INTO user_preferences (user_id, reminder_freq, reminder_time, last_reminded_on, updated_at)
          VALUES (?, COALESCE((SELECT reminder_freq FROM user_preferences WHERE user_id = ?), 'daily'),
                  COALESCE((SELECT reminder_time FROM user_preferences WHERE user_id = ?), '08:30'),
                  ?, strftime('%Y-%m-%d %H:%M:%f', 'now'))
          ON CONFLICT(user_id) DO UPDATE SET
            last_reminded_on = excluded.last_reminded_on,
            updated_at = excluded.updated_at
        `).run(pref.user_id, pref.user_id, pref.user_id, todayYMD);
      } catch (e) {
        console.error('RemindersService: error al procesar usuario', pref.user_id, e);
      }
    }
  }
}
