import pool from '../config/database';
import settingsService from './settings.service';
import nextcloudService from './nextcloud.service';
import logger from '../utils/logger';
// ── Types ────────────────────────────────────────────────────────────────────

interface ScheduledMessageRule {
  id: string;
  name: string;
  message_type: string;
  trigger_mode: string;
  day_of_week: number | null;
  send_time: string | null;
  days_before_month_start: number | null;
  window_mode: string | null;
  window_days: number | null;
  target_room_token: string;
  target_room_name: string | null;
  template: string;
  extra_config: Record<string, unknown> | null;
  subscribable: boolean;
  allowed_groups: string[];
  last_sent_at: string | null;
  active: boolean;
  created_at: string;
  created_by: string | null;
  subscriber_count?: number;
  is_subscribed?: boolean;
}

interface RoomInfo {
  token: string;
  displayName: string;
  type: number;
}

interface RoomsResult {
  configured: boolean;
  data?: RoomInfo[];
  error?: string;
}

// ── Helpers ──────────────────────────────────────────────────────────────────

async function getBotCredentials(): Promise<{ username: string; appPassword: string } | null> {
  const setting = await settingsService.get('tool_config_nextcloud');
  if (!setting?.value || typeof setting.value !== 'object') return null;
  const cfg = setting.value as Record<string, unknown>;
  const username = typeof cfg.bot_username === 'string' ? cfg.bot_username : null;
  const appPassword = typeof cfg.bot_app_password === 'string' ? cfg.bot_app_password : null;
  if (!username || !appPassword) return null;
  return { username, appPassword };
}

function computeWindow(rule: ScheduledMessageRule): { startDate: Date; endDate: Date } {
  const now = new Date();
  if (rule.window_mode === 'rolling') {
    const endDate = new Date(now.getTime() + (rule.window_days ?? 7) * 86400000);
    return { startDate: now, endDate };
  }
  // calendar_month = next full calendar month
  const startDate = new Date(now.getFullYear(), now.getMonth() + 1, 1);
  const endDate = new Date(now.getFullYear(), now.getMonth() + 2, 0); // last day of next month
  return { startDate, endDate };
}

function renderTemplate(template: string, vars: Record<string, string>): string {
  return Object.entries(vars).reduce(
    (acc, [key, val]) => acc.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), val),
    template,
  );
}

function formatDate(d: Date): string {
  const day = String(d.getDate()).padStart(2, '0');
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const year = d.getFullYear();
  return `${day}.${month}.${year}`;
}

function formatMonth(d: Date): string {
  const months = [
    'Jänner', 'Februar', 'März', 'April', 'Mai', 'Juni',
    'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember',
  ];
  return `${months[d.getMonth()]} ${d.getFullYear()}`;
}

// ── Content Builders ─────────────────────────────────────────────────────────

async function buildEventSummary(startDate: Date, endDate: Date): Promise<{ items: string; count: string }> {
  const result = await pool.query(
    `SELECT titel, datum_von, datum_bis
     FROM veranstaltungen
     WHERE status != 'abgesagt'
       AND datum_von <= $2
       AND datum_bis >= $1
     ORDER BY datum_von ASC`,
    [startDate.toISOString(), endDate.toISOString()],
  );

  if (result.rows.length === 0) {
    return { items: 'Keine Veranstaltungen im Zeitraum.', count: '0' };
  }

  const lines = result.rows.map((r: Record<string, unknown>) => {
    const von = formatDate(new Date(r.datum_von as string));
    const bis = formatDate(new Date(r.datum_bis as string));
    return von === bis
      ? `- ${r.titel as string} (${von})`
      : `- ${r.titel as string} (${von} – ${bis})`;
  });

  return { items: lines.join('\n'), count: String(result.rows.length) };
}

async function buildBirthdayList(startDate: Date, endDate: Date): Promise<{ items: string; count: string }> {
  // Find members whose birthday (month+day) falls within the window
  const result = await pool.query(
    `SELECT vorname, nachname, geburtsdatum
     FROM mitglieder_profile
     WHERE status IN ('aktiv', 'kind', 'jugend', 'reserve')
       AND geburtsdatum IS NOT NULL
       AND (
         (EXTRACT(MONTH FROM geburtsdatum), EXTRACT(DAY FROM geburtsdatum))
         IN (
           SELECT EXTRACT(MONTH FROM d::date), EXTRACT(DAY FROM d::date)
           FROM generate_series($1::date, $2::date, '1 day'::interval) AS d
         )
       )
     ORDER BY EXTRACT(MONTH FROM geburtsdatum), EXTRACT(DAY FROM geburtsdatum)`,
    [startDate.toISOString().slice(0, 10), endDate.toISOString().slice(0, 10)],
  );

  if (result.rows.length === 0) {
    return { items: 'Keine Geburtstage im Zeitraum.', count: '0' };
  }

  const lines = result.rows.map((r: Record<string, unknown>) => {
    const geb = new Date(r.geburtsdatum as string);
    const day = String(geb.getDate()).padStart(2, '0');
    const month = String(geb.getMonth() + 1).padStart(2, '0');
    return `- ${r.vorname as string} ${r.nachname as string} (${day}.${month}.)`;
  });

  return { items: lines.join('\n'), count: String(result.rows.length) };
}

async function buildDienstjubilaeen(startDate: Date, endDate: Date): Promise<{ items: string; count: string }> {
  // Members hitting a 5-year milestone in the window
  const result = await pool.query(
    `SELECT vorname, nachname, eintrittsdatum
     FROM mitglieder_profile
     WHERE status IN ('aktiv', 'reserve')
       AND eintrittsdatum IS NOT NULL`,
  );

  const jubilare: Array<{ name: string; years: number; date: string }> = [];

  for (const r of result.rows) {
    const eintritt = new Date(r.eintrittsdatum as string);
    // Check each year the anniversary date falls in the window
    for (let y = startDate.getFullYear(); y <= endDate.getFullYear(); y++) {
      const anniversary = new Date(y, eintritt.getMonth(), eintritt.getDate());
      if (anniversary >= startDate && anniversary <= endDate) {
        const years = y - eintritt.getFullYear();
        if (years > 0 && years % 5 === 0) {
          jubilare.push({
            name: `${r.vorname as string} ${r.nachname as string}`,
            years,
            date: formatDate(anniversary),
          });
        }
      }
    }
  }

  if (jubilare.length === 0) {
    return { items: 'Keine Dienstjubiläen im Zeitraum.', count: '0' };
  }

  jubilare.sort((a, b) => a.years - b.years);
  const lines = jubilare.map(j => `- ${j.name}: ${j.years} Jahre (${j.date})`);

  return { items: lines.join('\n'), count: String(jubilare.length) };
}

async function buildFahrzeugStatus(): Promise<{ items: string; count: string }> {
  const result = await pool.query(
    `SELECT bezeichnung, kurzname, status, status_bemerkung
     FROM fahrzeuge
     WHERE status != 'einsatzbereit'
     ORDER BY bezeichnung`,
  );

  if (result.rows.length === 0) {
    return { items: 'Alle Fahrzeuge einsatzbereit.', count: '0' };
  }

  const lines = result.rows.map((r: Record<string, unknown>) => {
    const name = (r.kurzname as string) || (r.bezeichnung as string);
    const remark = r.status_bemerkung ? ` – ${r.status_bemerkung as string}` : '';
    return `- ${name} (${r.status as string})${remark}`;
  });

  return { items: lines.join('\n'), count: String(result.rows.length) };
}

async function buildBestellungen(minDaysOverdue: number): Promise<{ items: string; count: string }> {
  const result = await pool.query(
    `SELECT bezeichnung, status, erstellt_am
     FROM bestellungen
     WHERE status IN ('erstellt', 'bestellt', 'teillieferung')
       AND erstellt_am < NOW() - ($1 || ' days')::interval
     ORDER BY erstellt_am ASC`,
    [minDaysOverdue],
  );

  if (result.rows.length === 0) {
    return { items: 'Keine offenen Bestellungen.', count: '0' };
  }

  const lines = result.rows.map((r: Record<string, unknown>) => {
    const created = formatDate(new Date(r.erstellt_am as string));
    return `- ${r.bezeichnung as string} (${r.status as string}, erstellt ${created})`;
  });

  return { items: lines.join('\n'), count: String(result.rows.length) };
}

// ── CRUD ─────────────────────────────────────────────────────────────────────

async function getAll(): Promise<ScheduledMessageRule[]> {
  const result = await pool.query(
    'SELECT * FROM scheduled_message_rules ORDER BY created_at DESC',
  );
  return result.rows;
}

async function getById(id: string, userId?: string): Promise<ScheduledMessageRule | null> {
  const result = await pool.query(
    `SELECT r.*,
            (SELECT COUNT(*)::int FROM scheduled_message_subscriptions WHERE rule_id = $1) AS subscriber_count,
            (SELECT EXISTS(SELECT 1 FROM scheduled_message_subscriptions WHERE rule_id = $1 AND user_id = $2)) AS is_subscribed
     FROM scheduled_message_rules r
     WHERE r.id = $1`,
    [id, userId ?? null],
  );
  return result.rows[0] ?? null;
}

async function create(
  data: Omit<ScheduledMessageRule, 'id' | 'last_sent_at' | 'created_at' | 'created_by' | 'subscriber_count' | 'is_subscribed'>,
  userId: string,
): Promise<ScheduledMessageRule> {
  const result = await pool.query(
    `INSERT INTO scheduled_message_rules
       (name, message_type, trigger_mode, day_of_week, send_time,
        days_before_month_start, window_mode, window_days,
        target_room_token, target_room_name, template, extra_config,
        subscribable, allowed_groups, active, created_by)
     VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
     RETURNING *`,
    [
      data.name, data.message_type, data.trigger_mode, data.day_of_week, data.send_time,
      data.days_before_month_start, data.window_mode, data.window_days,
      data.target_room_token, data.target_room_name, data.template,
      data.extra_config ? JSON.stringify(data.extra_config) : null,
      data.subscribable, data.allowed_groups, data.active, userId,
    ],
  );
  return result.rows[0];
}

async function update(
  id: string,
  data: Partial<Omit<ScheduledMessageRule, 'id' | 'created_at' | 'created_by' | 'subscriber_count' | 'is_subscribed'>>,
): Promise<ScheduledMessageRule | null> {
  const keys = Object.keys(data) as Array<keyof typeof data>;
  if (keys.length === 0) return getById(id);

  const setClauses: string[] = [];
  const values: unknown[] = [];
  let paramIndex = 1;

  for (const key of keys) {
    setClauses.push(`${key} = $${paramIndex}`);
    const val = data[key];
    if (key === 'extra_config' && val !== null && val !== undefined) {
      values.push(JSON.stringify(val));
    } else {
      values.push(val);
    }
    paramIndex++;
  }

  values.push(id);
  const result = await pool.query(
    `UPDATE scheduled_message_rules SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
    values,
  );
  return result.rows[0] ?? null;
}

async function remove(id: string): Promise<boolean> {
  const result = await pool.query('DELETE FROM scheduled_message_rules WHERE id = $1', [id]);
  return (result.rowCount ?? 0) > 0;
}

// ── Rooms ────────────────────────────────────────────────────────────────────

async function getRooms(): Promise<RoomsResult> {
  const creds = await getBotCredentials();
  if (!creds) {
    return { configured: false };
  }
  try {
    const conversations = await nextcloudService.getAllConversations(creds.username, creds.appPassword);
    return {
      configured: true,
      data: conversations.map(c => ({
        token: c.token,
        displayName: c.displayName,
        type: c.type,
      })),
    };
  } catch (error) {
    logger.error('scheduledMessages.getRooms failed', {
      error: error instanceof Error ? error.message : String(error),
    });
    return { configured: true, data: [], error: 'Verbindung zu Nextcloud fehlgeschlagen' };
  }
}

// ── Subscriptions ────────────────────────────────────────────────────────────

async function subscribe(ruleId: string, userId: string, roomToken: string): Promise<void> {
  await pool.query(
    `INSERT INTO scheduled_message_subscriptions (rule_id, user_id, room_token)
     VALUES ($1, $2, $3)
     ON CONFLICT (rule_id, user_id) DO NOTHING`,
    [ruleId, userId, roomToken],
  );
}

async function unsubscribe(ruleId: string, userId: string): Promise<void> {
  await pool.query(
    'DELETE FROM scheduled_message_subscriptions WHERE rule_id = $1 AND user_id = $2',
    [ruleId, userId],
  );
}

async function getSubscriptionsForUser(userId: string): Promise<Array<ScheduledMessageRule & { subscription_room_token: string }>> {
  const result = await pool.query(
    `SELECT r.*, s.room_token AS subscription_room_token
     FROM scheduled_message_rules r
     INNER JOIN scheduled_message_subscriptions s ON s.rule_id = r.id
     WHERE s.user_id = $1
     ORDER BY r.name`,
    [userId],
  );
  return result.rows;
}

// ── Build & Send ─────────────────────────────────────────────────────────────

async function buildAndSend(rule: ScheduledMessageRule): Promise<void> {
  const creds = await getBotCredentials();
  if (!creds) {
    logger.warn('scheduledMessages.buildAndSend: no bot credentials configured');
    return;
  }

  let vars: Record<string, string>;

  if (rule.message_type === 'fahrzeug_status') {
    vars = await buildFahrzeugStatus();
  } else if (rule.message_type === 'bestellungen') {
    const minDays = (rule.extra_config?.min_days_overdue as number) ?? 14;
    vars = await buildBestellungen(minDays);
  } else {
    const { startDate, endDate } = computeWindow(rule);
    const windowVars: Record<string, string> = {
      date: formatDate(new Date()),
      window: `${formatDate(startDate)} – ${formatDate(endDate)}`,
      month: formatMonth(startDate),
    };

    switch (rule.message_type) {
      case 'event_summary': {
        const data = await buildEventSummary(startDate, endDate);
        vars = { ...windowVars, ...data };
        break;
      }
      case 'birthday_list': {
        const data = await buildBirthdayList(startDate, endDate);
        vars = { ...windowVars, ...data };
        break;
      }
      case 'dienstjubilaeen': {
        const data = await buildDienstjubilaeen(startDate, endDate);
        vars = { ...windowVars, ...data };
        break;
      }
      default:
        logger.warn(`scheduledMessages: unknown message_type "${rule.message_type}"`);
        return;
    }
  }

  // Add common vars
  vars.date = vars.date ?? formatDate(new Date());

  const message = renderTemplate(rule.template, vars);

  // Send to target room
  try {
    await nextcloudService.sendMessage(rule.target_room_token, message, creds.username, creds.appPassword);
  } catch (error) {
    logger.error('scheduledMessages: failed to send to target room', {
      ruleId: rule.id,
      roomToken: rule.target_room_token,
      error: error instanceof Error ? error.message : String(error),
    });
  }

  // DM subscribers
  const subs = await pool.query(
    'SELECT user_id, room_token FROM scheduled_message_subscriptions WHERE rule_id = $1',
    [rule.id],
  );

  for (const sub of subs.rows) {
    try {
      await nextcloudService.sendMessage(
        sub.room_token as string,
        message,
        creds.username,
        creds.appPassword,
      );
    } catch (error) {
      logger.error('scheduledMessages: failed to DM subscriber', {
        ruleId: rule.id,
        userId: sub.user_id,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }
}

// ── Vehicle Event (fire-and-forget) ──────────────────────────────────────────

async function sendVehicleEvent(vehicleId: string): Promise<void> {
  try {
    const creds = await getBotCredentials();
    if (!creds) return;

    // Fetch vehicle info
    const vResult = await pool.query(
      'SELECT bezeichnung, kurzname, status, status_bemerkung FROM fahrzeuge WHERE id = $1',
      [vehicleId],
    );
    if (vResult.rows.length === 0) return;

    const vehicle = vResult.rows[0];
    const name = (vehicle.kurzname as string) || (vehicle.bezeichnung as string);
    const remark = vehicle.status_bemerkung ? ` – ${vehicle.status_bemerkung as string}` : '';

    // Find active fahrzeug_event rules
    const rulesResult = await pool.query(
      `SELECT * FROM scheduled_message_rules
       WHERE message_type = 'fahrzeug_event'
         AND trigger_mode = 'event'
         AND active = true`,
    );

    for (const rule of rulesResult.rows as ScheduledMessageRule[]) {
      const vars: Record<string, string> = {
        items: `${name} (${vehicle.status as string})${remark}`,
        count: '1',
        date: formatDate(new Date()),
        vehicle_name: name,
        vehicle_status: vehicle.status as string,
        vehicle_remark: (vehicle.status_bemerkung as string) || '',
      };

      const message = renderTemplate(rule.template, vars);

      try {
        await nextcloudService.sendMessage(rule.target_room_token, message, creds.username, creds.appPassword);
      } catch (error) {
        logger.error('scheduledMessages: fahrzeug_event send failed', {
          ruleId: rule.id,
          vehicleId,
          error: error instanceof Error ? error.message : String(error),
        });
      }

      // DM subscribers
      const subs = await pool.query(
        'SELECT user_id, room_token FROM scheduled_message_subscriptions WHERE rule_id = $1',
        [rule.id],
      );
      for (const sub of subs.rows) {
        try {
          await nextcloudService.sendMessage(sub.room_token as string, message, creds.username, creds.appPassword);
        } catch (error) {
          logger.error('scheduledMessages: fahrzeug_event DM failed', {
            ruleId: rule.id,
            userId: sub.user_id,
            error: error instanceof Error ? error.message : String(error),
          });
        }
      }
    }
  } catch (error) {
    logger.error('scheduledMessages.sendVehicleEvent failed', {
      vehicleId,
      error: error instanceof Error ? error.message : String(error),
    });
  }
}

// ── Bot Room ──────────────────────────────────────────────────────────────────

/**
 * Finds or creates a 1:1 Nextcloud Talk room between the bot and the given user.
 * Returns the room token, null if bot isn't configured, or 'not_connected' if the
 * user hasn't linked their Nextcloud account.
 */
async function getOrCreateBotRoom(userId: string): Promise<string | null | 'not_connected'> {
  const creds = await getBotCredentials();
  if (!creds) return null;

  // Look up the user's Nextcloud login name
  const userResult = await pool.query(
    'SELECT nextcloud_login_name FROM users WHERE id = $1',
    [userId],
  );
  const nextcloudLoginName = userResult.rows[0]?.nextcloud_login_name as string | null;
  if (!nextcloudLoginName) return 'not_connected';

  // Use createRoom(type=1) — Nextcloud returns existing 1:1 if it already exists
  const { token } = await nextcloudService.createRoom(
    1,
    nextcloudLoginName,
    undefined,
    creds.username,
    creds.appPassword,
  );
  return token;
}



interface OneTimeMessage {
  id: string;
  message: string;
  target_room_token: string;
  target_room_name: string | null;
  send_at: string;
  created_by: string | null;
  created_at: string;
}

async function getOneTimeMessages(): Promise<OneTimeMessage[]> {
  const result = await pool.query(
    'SELECT * FROM scheduled_one_time_messages ORDER BY send_at ASC',
  );
  return result.rows;
}

async function createOneTimeMessage(
  data: Pick<OneTimeMessage, 'message' | 'target_room_token' | 'target_room_name' | 'send_at'>,
  userId: string,
): Promise<OneTimeMessage> {
  const result = await pool.query(
    `INSERT INTO scheduled_one_time_messages
       (message, target_room_token, target_room_name, send_at, created_by)
     VALUES ($1, $2, $3, $4, $5)
     RETURNING *`,
    [data.message, data.target_room_token, data.target_room_name ?? null, data.send_at, userId],
  );
  return result.rows[0];
}

async function deleteOneTimeMessage(id: string): Promise<boolean> {
  const result = await pool.query(
    'DELETE FROM scheduled_one_time_messages WHERE id = $1',
    [id],
  );
  return (result.rowCount ?? 0) > 0;
}

async function sendDueOneTimeMessages(): Promise<void> {
  const creds = await getBotCredentials();
  if (!creds) return;

  const result = await pool.query(
    `SELECT * FROM scheduled_one_time_messages WHERE send_at <= NOW() ORDER BY send_at ASC`,
  );

  for (const row of result.rows as OneTimeMessage[]) {
    try {
      await nextcloudService.sendMessage(row.target_room_token, row.message, creds.username, creds.appPassword);
      await pool.query('DELETE FROM scheduled_one_time_messages WHERE id = $1', [row.id]);
      logger.info('scheduledMessages: sent one-time message', { id: row.id, room: row.target_room_token });
    } catch (error) {
      logger.error('scheduledMessages: failed to send one-time message', {
        id: row.id,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }
}

// ── Export ────────────────────────────────────────────────────────────────────

const scheduledMessagesService = {
  getAll,
  getById,
  create,
  update,
  delete: remove,
  getRooms,
  subscribe,
  unsubscribe,
  getSubscriptionsForUser,
  buildAndSend,
  sendVehicleEvent,
  getOrCreateBotRoom,
  getOneTimeMessages,
  createOneTimeMessage,
  deleteOneTimeMessage,
  sendDueOneTimeMessages,
};

export type { ScheduledMessageRule, RoomInfo, RoomsResult, OneTimeMessage };
export default scheduledMessagesService;
