import { Cron } from 'croner';
import type { Pool, PoolClient } from 'pg';

export interface ScheduledJobSpec {
  name: string;
  pattern: string;
  path: string;
  secretName: 'BACKUP_CRON_SECRET' | 'RENOVACOES_CRON_SECRET' | 'VD_PENDENCIAS_CRON_SECRET';
  secretHeader: string;
}

type AppRequest = (path: string, init: RequestInit) => Response | Promise<Response>;

export const scheduledJobSpecs: readonly ScheduledJobSpec[] = Object.freeze([
  {
    name: 'tonline-processar-pendencias-vd',
    pattern: '*/2 * * * *',
    path: '/make-server-7249dcd9/vendas-dinheiro/processar-pendencias',
    secretName: 'VD_PENDENCIAS_CRON_SECRET',
    secretHeader: 'X-Vd-Pendencias-Secret',
  },
  {
    name: 'tonline-processar-renovacoes-diario',
    pattern: '0 5 * * *',
    path: '/admin-server-7249dcd9/subscricoes/processar-renovacoes',
    secretName: 'RENOVACOES_CRON_SECRET',
    secretHeader: 'X-Renovacoes-Secret',
  },
  {
    name: 'tonline-backup-automatico-diario',
    pattern: '0 20 * * *',
    path: '/admin-server-7249dcd9/backups/automatico-diario',
    secretName: 'BACKUP_CRON_SECRET',
    secretHeader: 'X-Backup-Secret',
  },
]);

async function responseMessage(response: Response): Promise<string> {
  try {
    const payload = await response.clone().json() as Record<string, unknown>;
    const message = payload.error ?? payload.message;
    if (typeof message === 'string') return message.slice(0, 500);
  } catch {
    // Fall back to the HTTP status below.
  }
  return `HTTP ${response.status}`;
}

export async function executeScheduledJob(
  pool: Pool,
  spec: ScheduledJobSpec,
  request: AppRequest,
): Promise<void> {
  const secret = process.env[spec.secretName]?.trim();
  if (!secret) {
    console.error(`[scheduler] ${spec.name} skipped: ${spec.secretName} is not configured.`);
    return;
  }

  let client: PoolClient | undefined;
  let locked = false;
  try {
    client = await pool.connect();
    const lock = await client.query<{ acquired: boolean }>(
      'SELECT pg_try_advisory_lock(hashtextextended($1, 0)) AS acquired',
      [spec.name],
    );
    locked = lock.rows[0]?.acquired === true;
    if (!locked) {
      console.warn(`[scheduler] ${spec.name} is already running in another process.`);
      return;
    }

    const response = await request(spec.path, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        [spec.secretHeader]: secret,
      },
      body: '{}',
    });
    if (!response.ok) {
      console.error(
        `[scheduler] ${spec.name} failed (${response.status}): ${await responseMessage(response)}`,
      );
      return;
    }
    console.log(`[scheduler] ${spec.name} completed.`);
  } catch (error) {
    console.error(
      `[scheduler] ${spec.name} failed:`,
      error instanceof Error ? error.message : String(error),
    );
  } finally {
    if (locked && client) {
      await client.query('SELECT pg_advisory_unlock(hashtextextended($1, 0))', [spec.name])
        .catch(() => undefined);
    }
    client?.release();
  }
}

export interface LocalScheduler {
  stop(): void;
}

export interface SchedulerOptions {
  enabled: boolean;
  timezone: string;
}

export function startScheduler(
  request: AppRequest,
  pool: Pool,
  options: SchedulerOptions,
): LocalScheduler {
  if (!options.enabled) {
    console.log('[scheduler] disabled by SCHEDULER_ENABLED=false.');
    return { stop: () => undefined };
  }

  const jobs = scheduledJobSpecs.map((spec) =>
    new Cron(
      spec.pattern,
      {
        name: spec.name,
        protect: true,
        timezone: options.timezone,
      },
      () => executeScheduledJob(pool, spec, request),
    ),
  );

  console.log(
    `[scheduler] ${jobs.length} jobs active in timezone ${options.timezone}.`,
  );
  return {
    stop() {
      for (const job of jobs) job.stop();
    },
  };
}
