import { db } from '../db/client.js';
import type { RequestAuditContext } from '../utils/request-audit.js';

const TOKEN_SETTING_ACTIVITIES_TABLE = 'token_setting_activities';

export type TokenSettingActivityRecord = {
  id: number;
  settingId: string;
  activityType: 'created' | 'updated' | 'deleted';
  actorLabel: string | null;
  summary: string;
  detail: string | null;
  clientIp: string | null;
  externalIp: string | null;
  forwardedFor: string | null;
  realIp: string | null;
  host: string | null;
  origin: string | null;
  referer: string | null;
  userAgent: string | null;
  clientId: string | null;
  createdAt: string;
};

export type TokenSettingActivityInput = {
  settingId: string;
  activityType: TokenSettingActivityRecord['activityType'];
  actorLabel?: string | null;
  summary: string;
  detail?: string | null;
  audit?: RequestAuditContext | null;
};

function normalizeText(value: unknown) {
  return typeof value === 'string' ? value.trim() : '';
}

function normalizeOptionalText(value: unknown) {
  const normalized = normalizeText(value);
  return normalized || null;
}

function normalizeDateTime(value: unknown) {
  const normalized = normalizeText(value);
  if (!normalized) {
    return null;
  }
  const timestamp = Date.parse(normalized);
  return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
}

export async function ensureTokenSettingActivityTable() {
  const hasTable = await db.schema.hasTable(TOKEN_SETTING_ACTIVITIES_TABLE);

  if (!hasTable) {
    await db.schema.createTable(TOKEN_SETTING_ACTIVITIES_TABLE, (table) => {
      table.increments('id').primary();
      table.string('setting_id', 120).notNullable().index();
      table.string('activity_type', 40).notNullable();
      table.string('actor_label', 120).nullable();
      table.string('summary', 400).notNullable();
      table.text('detail').nullable();
      table.string('client_ip', 120).nullable();
      table.string('external_ip', 120).nullable();
      table.text('forwarded_for').nullable();
      table.string('real_ip', 120).nullable();
      table.string('host', 255).nullable();
      table.text('origin').nullable();
      table.text('referer').nullable();
      table.text('user_agent').nullable();
      table.string('client_id', 255).nullable();
      table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
    });
    return;
  }

  const requiredColumns: Array<[string, (table: any) => void]> = [
    ['setting_id', (table) => table.string('setting_id', 120).notNullable().defaultTo('').index()],
    ['activity_type', (table) => table.string('activity_type', 40).notNullable().defaultTo('updated')],
    ['actor_label', (table) => table.string('actor_label', 120).nullable()],
    ['summary', (table) => table.string('summary', 400).notNullable().defaultTo('')],
    ['detail', (table) => table.text('detail').nullable()],
    ['client_ip', (table) => table.string('client_ip', 120).nullable()],
    ['external_ip', (table) => table.string('external_ip', 120).nullable()],
    ['forwarded_for', (table) => table.text('forwarded_for').nullable()],
    ['real_ip', (table) => table.string('real_ip', 120).nullable()],
    ['host', (table) => table.string('host', 255).nullable()],
    ['origin', (table) => table.text('origin').nullable()],
    ['referer', (table) => table.text('referer').nullable()],
    ['user_agent', (table) => table.text('user_agent').nullable()],
    ['client_id', (table) => table.string('client_id', 255).nullable()],
    ['created_at', (table) => table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now())],
  ];

  for (const [columnName, createColumn] of requiredColumns) {
    const hasColumn = await db.schema.hasColumn(TOKEN_SETTING_ACTIVITIES_TABLE, columnName);
    if (!hasColumn) {
      await db.schema.alterTable(TOKEN_SETTING_ACTIVITIES_TABLE, (table) => {
        createColumn(table);
      });
    }
  }
}

export async function appendTokenSettingActivity(input: TokenSettingActivityInput) {
  await ensureTokenSettingActivityTable();

  await db(TOKEN_SETTING_ACTIVITIES_TABLE).insert({
    setting_id: normalizeText(input.settingId),
    activity_type: input.activityType,
    actor_label: normalizeOptionalText(input.actorLabel),
    summary: normalizeText(input.summary),
    detail: normalizeOptionalText(input.detail),
    client_ip: normalizeOptionalText(input.audit?.clientIp),
    external_ip: normalizeOptionalText(input.audit?.externalIp),
    forwarded_for: normalizeOptionalText(input.audit?.forwardedFor),
    real_ip: normalizeOptionalText(input.audit?.realIp),
    host: normalizeOptionalText(input.audit?.host),
    origin: normalizeOptionalText(input.audit?.origin),
    referer: normalizeOptionalText(input.audit?.referer),
    user_agent: normalizeOptionalText(input.audit?.userAgent),
    client_id: normalizeOptionalText(input.audit?.clientId),
    created_at: db.fn.now(),
  });
}

export async function listTokenSettingActivities(settingId: string) {
  await ensureTokenSettingActivityTable();

  const rows = await db(TOKEN_SETTING_ACTIVITIES_TABLE)
    .select('*')
    .where({ setting_id: normalizeText(settingId) })
    .orderBy('created_at', 'desc')
    .limit(200);

  return rows.map((row) => ({
    id: Number(row.id),
    settingId: normalizeText(row.setting_id),
    activityType: (normalizeText(row.activity_type) as TokenSettingActivityRecord['activityType']) || 'updated',
    actorLabel: normalizeOptionalText(row.actor_label),
    summary: normalizeText(row.summary),
    detail: normalizeOptionalText(row.detail),
    clientIp: normalizeOptionalText(row.client_ip),
    externalIp: normalizeOptionalText(row.external_ip),
    forwardedFor: normalizeOptionalText(row.forwarded_for),
    realIp: normalizeOptionalText(row.real_ip),
    host: normalizeOptionalText(row.host),
    origin: normalizeOptionalText(row.origin),
    referer: normalizeOptionalText(row.referer),
    userAgent: normalizeOptionalText(row.user_agent),
    clientId: normalizeOptionalText(row.client_id),
    createdAt: normalizeDateTime(row.created_at) ?? new Date().toISOString(),
  }));
}
