import type { Env, SiteSettings, UserRole } from '../env';
import { all } from '../db/queries';

const CACHE_KEY = 'settings:cache';
const CACHE_TTL = 300;

export const DEFAULT_SETTINGS: SiteSettings = {
  site_name: 'Wemoments',
  site_description: '',
  site_avatar_key: '',
  top_image_key: '',
  top_video_key: '',
  avatar_link: '',
  avatar_mirror: '',
  tencent_map_key: '',
  beian_info: '',
  list_collapse: true,
  custom_css: '',
  custom_js: '',
  analytics_code: '',
  allow_register: true,
  default_role: 'subscriber',
  comment_mode: 'registered',
  comment_review: false,
  upload_max_image_mb: 8,
  upload_max_video_mb: 50,
  cdn_base_url: ''
};

export const SETTING_KEYS = Object.keys(DEFAULT_SETTINGS) as Array<keyof SiteSettings>;

type SettingValue = SiteSettings[keyof SiteSettings];

function normalizeValue<K extends keyof SiteSettings>(key: K, value: unknown): SiteSettings[K] {
  const fallback = DEFAULT_SETTINGS[key];
  if (typeof fallback === 'boolean') return Boolean(value) as SiteSettings[K];
  if (typeof fallback === 'number') {
    const n = Number(value);
    return (Number.isFinite(n) ? n : fallback) as SiteSettings[K];
  }
  if (key === 'default_role') {
    return (['subscriber', 'author', 'admin'].includes(String(value)) ? value : fallback) as SiteSettings[K];
  }
  if (key === 'comment_mode') {
    return (['everyone', 'registered', 'closed'].includes(String(value)) ? value : fallback) as SiteSettings[K];
  }
  return (typeof value === 'string' ? value : String(value ?? fallback)) as SiteSettings[K];
}

export function parseStoredValue(raw: string): unknown {
  try {
    return JSON.parse(raw);
  } catch {
    return raw;
  }
}

export async function getSettings(env: Env['Bindings']): Promise<SiteSettings> {
  const cached = await env.KV.get<Partial<SiteSettings>>(CACHE_KEY, 'json');
  if (cached) return { ...DEFAULT_SETTINGS, ...cached };

  const rows = await all<{ key: string; value: string }>(env.DB.prepare('SELECT key, value FROM wm_settings'));
  const settings: SiteSettings = { ...DEFAULT_SETTINGS };
  for (const row of rows) {
    if (SETTING_KEYS.includes(row.key as keyof SiteSettings)) {
      const key = row.key as keyof SiteSettings;
      settings[key] = normalizeValue(key, parseStoredValue(row.value)) as never;
    }
  }
  await env.KV.put(CACHE_KEY, JSON.stringify(settings), { expirationTtl: CACHE_TTL });
  return settings;
}

export async function setSetting<K extends keyof SiteSettings>(env: Env['Bindings'], key: K, value: SiteSettings[K]): Promise<SiteSettings> {
  const normalized = normalizeValue(key, value);
  await env.DB.prepare('INSERT INTO wm_settings(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value')
    .bind(key, JSON.stringify(normalized))
    .run();
  const settings = await getSettingsFresh(env);
  await env.KV.put(CACHE_KEY, JSON.stringify(settings), { expirationTtl: CACHE_TTL });
  return settings;
}

export async function setSettings(env: Env['Bindings'], values: Partial<SiteSettings>): Promise<SiteSettings> {
  const batch: D1PreparedStatement[] = [];
  for (const [key, value] of Object.entries(values) as Array<[keyof SiteSettings, SettingValue]>) {
    if (!SETTING_KEYS.includes(key)) continue;
    batch.push(
      env.DB.prepare('INSERT INTO wm_settings(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').bind(
        key,
        JSON.stringify(normalizeValue(key, value))
      )
    );
  }
  if (batch.length > 0) await env.DB.batch(batch);
  const settings = await getSettingsFresh(env);
  await env.KV.put(CACHE_KEY, JSON.stringify(settings), { expirationTtl: CACHE_TTL });
  return settings;
}

async function getSettingsFresh(env: Env['Bindings']): Promise<SiteSettings> {
  await env.KV.delete(CACHE_KEY);
  const rows = await all<{ key: string; value: string }>(env.DB.prepare('SELECT key, value FROM wm_settings'));
  const settings: SiteSettings = { ...DEFAULT_SETTINGS };
  for (const row of rows) {
    if (SETTING_KEYS.includes(row.key as keyof SiteSettings)) {
      const key = row.key as keyof SiteSettings;
      settings[key] = normalizeValue(key, parseStoredValue(row.value)) as never;
    }
  }
  return settings;
}

export function isValidRole(role: string): role is UserRole {
  return role === 'subscriber' || role === 'author' || role === 'admin';
}
