import type { Database } from 'bun:sqlite';
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 { formatTaskLine, formatDatePart } from './commands/shared';
import { AllowedGroups } from './allowed-groups';
import { Metrics } from './metrics';
import { getDb } from '../db/locator';

type UserPreference = {
  user_id: string;
  reminder_freq: 'daily' | 'weekly' | 'weekdays' | 'off';
  reminder_time: string;
  last_reminded_on: string | null;
};

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function parseHmMinutes(hm: string): number {
  const [h, m] = String(hm).split(':');
  return (parseInt(h || '0', 10) || 0) * 60 + (parseInt(m || '0', 10) || 0);
}

function graceMinutes(): number {
  const raw = Number(process.env.REMINDERS_GRACE_MINUTES);
  return Number.isFinite(raw) && raw >= 0 ? Math.min(Math.floor(raw), 180) : 60;
}

function enforceGatingEnabled(): boolean {
  return String(process.env.GROUP_GATING_MODE || 'off').toLowerCase() === 'enforce';
}

function filterTasksByGating(tasks: any[], enforce: boolean): any[] {
  if (!enforce) return tasks;
  return tasks.filter(t => !t.group_id || AllowedGroups.isAllowed(t.group_id));
}

// ---------------------------------------------------------------------------
// Preference evaluation
// ---------------------------------------------------------------------------

type SkipReason = 'already_reminded' | 'before_time' | 'wrong_day' | 'outside_window';

/**
 * Evaluates whether a reminder should be sent for this preference+time.
 * Returns null if we should proceed, or a SkipReason if we should not.
 */
function evaluatePreference(
  pref: UserPreference,
  todayYMD: string,
  nowMin: number,
  weekday: string,
): { skip: SkipReason } | null {
  // Already reminded today
  if (pref.last_reminded_on === todayYMD) return { skip: 'already_reminded' };
  if (!pref.reminder_time) return { skip: 'already_reminded' };

  const cfgMin = parseHmMinutes(pref.reminder_time);

  // Before scheduled time
  if (nowMin < cfgMin) return { skip: 'before_time' };

  const grace = graceMinutes();

  // Outside grace window → skip with metric
  if (nowMin > cfgMin + grace) return { skip: 'outside_window' };

  // Weekdays: Mon-Fri only
  if (pref.reminder_freq === 'weekdays' && (weekday === 'Sat' || weekday === 'Sun')) {
    return { skip: 'wrong_day' };
  }

  // Weekly: Monday only
  if (pref.reminder_freq === 'weekly' && weekday !== 'Mon') {
    return { skip: 'wrong_day' };
  }

  return null;
}

// ---------------------------------------------------------------------------
// Message building
// ---------------------------------------------------------------------------

async function buildTaskSections(
  items: any[],
  todayYMD: string,
): Promise<string[]> {
  const byGroup = new Map<string, any[]>();
  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[] = [];
  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: string) => (await ContactsService.getDisplayName(uid)) || uid),
      );
      const owner =
        (t.assignees?.length || 0) === 0
          ? `${ICONS.unassigned} sin responsable`
          : `${t.assignees!.length > 1 ? '👥' : '👤'} ${names.join(', ')}`;
      return formatTaskLine(t, owner, todayYMD);
    }));
    sections.push(...rendered);
  }
  return sections;
}

async function buildUnassignedSections(
  userId: string,
  enforce: boolean,
  todayYMD: string,
): Promise<string[]> {
  const sections: string[] = [];
  let memberGroups = GroupSyncService.getFreshMemberGroupsForUser(userId);
  if (enforce) memberGroups = memberGroups.filter(gid => AllowedGroups.isAllowed(gid));

  for (const gid of memberGroups) {
    const unassigned = TaskService.listGroupUnassigned(gid, 10);
    if (unassigned.length === 0) continue;

    const groupName = GroupSyncService.activeGroupsCache.get(gid) || gid;
    sections.push(bold(`${groupName} — Sin responsable`));

    const rendered = unassigned.map(t =>
      formatTaskLine(t, `${ICONS.unassigned} sin responsable`, todayYMD),
    );
    sections.push(...rendered);

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

async function buildReminderMessage(
  pref: UserPreference,
  items: any[],
  total: number,
  todayYMD: string,
): Promise<string> {
  const sections: string[] = [];

  // Header
  sections.push(
    pref.reminder_freq === 'weekly'
      ? `${ICONS.reminder} RECORDATORIO SEMANAL — TUS TAREAS`
      : `${ICONS.reminder} RECORDATORIO DIARIO — TUS TAREAS`,
  );

  // User's tasks grouped by group
  sections.push(...await buildTaskSections(items, todayYMD));

  // Overflow note
  if (total > items.length) {
    sections.push(italic(`… y ${total - items.length} más`));
  }

  // Optional: unassigned tasks from user's groups
  const includeUnassigned = String(
    process.env.REMINDERS_INCLUDE_UNASSIGNED_FROM_MEMBERSHIP || '',
  ).toLowerCase() === 'true';
  if (includeUnassigned) {
    const enforce = enforceGatingEnabled();
    sections.push(...await buildUnassignedSections(pref.user_id, enforce, todayYMD));
  }

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

// ---------------------------------------------------------------------------
// Database
// ---------------------------------------------------------------------------

function fetchPreferences(db: Database): UserPreference[] {
  return db.prepare(`
    SELECT user_id, reminder_freq, reminder_time, last_reminded_on
    FROM user_preferences
    WHERE reminder_freq IN ('daily', 'weekly', 'weekdays')
  `).all() as UserPreference[];
}

function markReminded(db: Database, userId: string, todayYMD: string): void {
  db.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(userId, userId, userId, todayYMD);
}

// ---------------------------------------------------------------------------
// RemindersService
// ---------------------------------------------------------------------------

export class RemindersService {

  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);
  }

  static start(): void {
    if (process.env.NODE_ENV === 'test' || this._running) return;
    this._running = true;
    this._timer = setInterval(() => {
      this.runOnce().catch(err => console.error('RemindersService runOnce error:', err));
    }, 60_000);
    setTimeout(() => this.runOnce().catch(() => {}), 5_000);
  }

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

  static async runOnce(now: Date = new Date()): Promise<void> {
    const db = getDb() as Database;
    const todayYMD = this.ymdInTZ(now);
    const nowHM = this.hmInTZ(now);
    const weekday = this.weekdayShortInTZ(now);

    const enforce = enforceGatingEnabled();
    if (enforce) {
      try { AllowedGroups.clearCache?.(); } catch {}
    }

    const prefs = fetchPreferences(db);

    for (const pref of prefs) {
      try {
        await processOnePreference(db, pref, todayYMD, nowHM, weekday, enforce);
      } catch (e) {
        console.error('RemindersService: error al procesar usuario', pref.user_id, e);
      }
    }
  }
}

// ---------------------------------------------------------------------------
// Per-preference processing (extracted from the loop)
// ---------------------------------------------------------------------------

async function processOnePreference(
  db: Database,
  pref: UserPreference,
  todayYMD: string,
  nowHM: string,
  weekday: string,
  enforce: boolean,
): Promise<void> {
  const nowMin = parseHmMinutes(nowHM);
  const evalResult = evaluatePreference(pref, todayYMD, nowMin, weekday);

  if (evalResult) {
    // Track metric for outside-window skips on valid weekdays
    if (evalResult.skip === 'outside_window') {
      const isWeekend = weekday === 'Sat' || weekday === 'Sun';
      const isValidDay = !(
        (pref.reminder_freq === 'weekdays' && isWeekend) ||
        (pref.reminder_freq === 'weekly' && weekday !== 'Mon')
      );
      if (isValidDay) {
        try { Metrics.inc('reminders_skipped_outside_window_total'); } catch {}
      }
    }
    return;
  }

  // Fetch and filter tasks
  const allPending = TaskService.listUserPending(pref.user_id, 1000);
  const filtered = filterTasksByGating(allPending, enforce);
  if (filtered.length === 0) return; // No tasks yet — don't mark as reminded

  const items = filtered.slice(0, 10);

  // Build and send message
  const message = await buildReminderMessage(pref, items, filtered.length, todayYMD);
  await ResponseQueue.add([{ recipient: pref.user_id, message }]);

  // Mark as sent
  markReminded(db, pref.user_id, todayYMD);
}
