import mysql, { type Pool, type PoolConnection, type RowDataPacket, type ResultSetHeader, type FieldPacket } from 'mysql2/promise';
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { logger } from '../src/utils/logger.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

interface DbConfig {
  host: string;
  port: number;
  database: string;
  user: string;
  password: string;
}

interface JwtConfig {
  secret: string;
  expiresIn: string;
}

interface EnvConfig {
  db: DbConfig;
}

const env = process.env.NODE_ENV ?? 'development';

// config.json holds only DB credentials, keyed by NODE_ENV — the one thing
// that genuinely differs per environment. It's gitignored (see
// .gitignore), so it never leaves this machine/server. Only
// config.example.json (safe placeholders, no real secrets) is committed;
// copy it to config.json and fill in real values to get started.
//
// Port, host, and the JWT secret are NOT per-environment — this server
// always listens on the same port and signs tokens with the same secret
// regardless of which DB it's pointed at — so those come from process.env
// (loaded from .env) instead.
const configPath = join(__dirname, 'config.json');
if (!existsSync(configPath)) {
  throw new Error(
    'Missing backend/config/config.json — copy backend/config/config.example.json to ' +
    'backend/config/config.json and fill in real DB credentials for each environment you use.',
  );
}
const allConfigs: Record<string, EnvConfig> = JSON.parse(readFileSync(configPath, 'utf-8'));
const resolvedConfig = allConfigs[env];
if (!resolvedConfig) {
  throw new Error(`No "${env}" block in backend/config/config.json — add one (see config.example.json for the shape).`);
}
// Reassigned with an explicit (non-optional) type so functions defined
// below — called later, not at module-init time — still see it as always
// defined; TS can't otherwise carry the narrowing from the guard above
// across a closure boundary.
const envConfig: EnvConfig = resolvedConfig;

const jwtConfig: JwtConfig = {
  secret: process.env.JWT_SECRET ?? (env === 'production' ? '' : 'dev-karur-chains-jwt-secret-2026'),
  expiresIn: process.env.JWT_EXPIRES_IN ?? '7d',
};

// No fallback secret exists for production (see above) — this only ever
// fires when JWT_SECRET truly wasn't set in .env.
if (env === 'production' && !jwtConfig.secret) {
  throw new Error('Refusing to start: JWT_SECRET must be set to a real secret in production (see .env).');
}

export function getServerConfig(): { port: number; host: string } {
  return {
    port: Number(process.env.PORT ?? 5000),
    host: process.env.HOST ?? '0.0.0.0',
  };
}

let pool: Pool | null = null;

export function getPool(): Pool {
  if (!pool) {
    pool = mysql.createPool({
      host: envConfig.db.host,
      port: envConfig.db.port,
      database: envConfig.db.database,
      user: envConfig.db.user,
      password: envConfig.db.password,
      waitForConnections: true,
      connectionLimit: 10,
      queueLimit: 0,
      enableKeepAlive: true,
      keepAliveInitialDelay: 0,
      // Without this, mysql2 returns DATE columns as JS Date objects built
      // at *local* midnight, which every `.toISOString()` call then
      // reinterprets in UTC — on a server running ahead of UTC (e.g. IST),
      // that silently rolls the date back a day (order_date/delivery_date/
      // dispatch_date/cancel_date are all DATE columns). Returning them as
      // plain 'YYYY-MM-DD' strings sidesteps the timezone conversion
      // entirely, so the date that was stored is the date that comes back.
      dateStrings: ['DATE'],
    });
    // mysql2's Pool is an EventEmitter — a fatal connection error (e.g. the
    // DB restarting, a network blip) emits 'error' here, and Node crashes
    // the whole process if an 'error' event has no listener. Individual
    // queries already surface their own errors to their caller via the
    // rejected promise; this is just the safety net for the pool-level event.
    // (mysql2's promise-Pool types don't declare this event even though it's
    // emitted at runtime, hence the cast.)
    (pool as unknown as NodeJS.EventEmitter).on('error', (err) => {
      logger.error({ err }, 'MySQL pool error');
    });
  }
  return pool;
}

export async function query<T extends RowDataPacket[]>(sql: string, params?: any[]): Promise<T> {
  const p = getPool();
  const [rows] = await p.query<T>(sql, params);
  return rows;
}

export async function execute(sql: string, params?: any[]): Promise<ResultSetHeader> {
  const p = getPool();
  const [result] = await p.execute<ResultSetHeader>(sql, params);
  return result;
}

export async function getConnection(): Promise<PoolConnection> {
  const p = getPool();
  return p.getConnection();
}

export async function queryWithConn<T extends RowDataPacket[]>(
  sql: string,
  params: any[] | undefined,
  conn?: PoolConnection,
): Promise<T> {
  if (conn) {
    const [rows] = await conn.query<T>(sql, params);
    return rows;
  }
  return query<T>(sql, params);
}

export async function executeWithConn(
  sql: string,
  params: any[] | undefined,
  conn?: PoolConnection,
): Promise<ResultSetHeader> {
  if (conn) {
    const [result] = await conn.execute<ResultSetHeader>(sql, params);
    return result;
  }
  return execute(sql, params);
}

export function getJwtConfig(): JwtConfig {
  return jwtConfig;
}

export async function testConnection(): Promise<boolean> {
  try {
    const p = getPool();
    await p.query('SELECT 1');
    return true;
  } catch {
    return false;
  }
}
