import { createDBPool, migratePSQL } from "@/internal/datastore";
import { createRedisDB } from "@/internal/datastore/redis";
import { env } from "@/lib/env";
import { logInfo } from "@/lib/logger";
import { UpdotAuthManager } from "@/lib/updot-auth";
import { serve } from "@hono/node-server";
import type { Context, Next } from "hono";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { HTTPException } from "hono/http-exception";
import { timeout } from "hono/timeout";
import type Redis from "ioredis";
import type { Kysely } from "kysely";
import type { DB } from "kysely-codegen";
import { AppRoutes } from "./routes";
import { initializeBackgroundServices } from "./services";

interface App {
  datastore: Kysely<DB>;
  memberDatastore: Kysely<any>;
  redisDB: Redis;
}

export async function main() {
  const cfg = {
    host: env.GetString("HOST"),
    port: env.GetInt("PORT"),
    datastore: {
      host: env.GetString("DATABASE_HOST"),
      port: env.GetInt("DATABASE_PORT"),
      database: env.GetString("DATABASE_NAME"),
      user: env.GetString("DATABASE_USER"),
      password: env.GetString("DATABASE_PWD"),
      max: env.GetInt("DATABASE_MAX_CONNECTIONS"),
      idleTimeoutMillis: env.GetInt("DATABASE_IDLE_TIMEOUT"),
      connectionTimeoutMillis: env.GetInt("DATABASE_CONNECTION_TIMEOUT"),
      query_timeout: env.GetInt("DATABASE_QUERY_TIMEOUT"),
      statement_timeout: env.GetInt("DATABASE_STATEMENT_TIMEOUT"),
      keepAlive: true,
      keepAliveInitialDelayMillis: 10000,
      ssl:
        env.GetString("DATABASE_HOST") !== "localhost" &&
          env.GetString("DATABASE_HOST") !== "127.0.0.1"
          ? { rejectUnauthorized: false }
          : undefined,
    },
    memberDatastore: {
      host: env.GetString("MEMBER_DB_HOST"),
      port: env.GetInt("MEMBER_DB_PORT"),
      database: env.GetString("MEMBER_DB_NAME"),
      user: env.GetString("MEMBER_DB_USER"),
      password: env.GetString("MEMBER_DB_PASSWORD"),
      max: process.env.MEMBER_DB_MAX_CONNECTIONS
        ? Number(process.env.MEMBER_DB_MAX_CONNECTIONS)
        : 20,
      idleTimeoutMillis: env.GetInt("DATABASE_IDLE_TIMEOUT") || 30000,
      connectionTimeoutMillis: env.GetInt("DATABASE_CONNECTION_TIMEOUT") || 10000,
      query_timeout: env.GetInt("DATABASE_QUERY_TIMEOUT"),
      statement_timeout: env.GetInt("DATABASE_STATEMENT_TIMEOUT"),
      keepAlive: true,
      keepAliveInitialDelayMillis: 10000,
      ssl:
        env.GetString("MEMBER_DB_HOST") !== "localhost" &&
          env.GetString("MEMBER_DB_HOST") !== "127.0.0.1"
          ? { rejectUnauthorized: false }
          : undefined,
    },
    // redis: {
    //   connection_url: env.GetString("REDIS_CONNECTION_URL"),
    // },
    redisOptions: {
      host: env.GetString("REDIS_HOST"),
      port: env.GetInt("REDIS_PORT"),
      username: env.GetString("REDIS_USERNAME"),
      password: env.GetString("REDIS_PASSWORD"),
      connectTimeout: env.GetInt("REDIS_CONNECTION_TIMEOUT"),
      enableOfflineQueue: true,
    },
    googleCloud: {
      bucket: env.GetString("GOOGLE_CLOUD_STORAGE_BUCKET_NAME"),
      serviceAccount: env.GetString("GOOGLE_SERVICE_ACCOUNT_CREDENTIALS"),
      projectID: env.GetString("GOOGLE_CLOUD_PROJECT_ID"),
    },
  };

  const app = new Hono<{ Variables: App }>();

  app.use(
    "*",
    cors({
      origin: [
        env.GetString("WEB_URL"),
        env.GetString("IDP_WEB_URL"),
        "http://localhost:3000",
        "http://localhost:3010",
        "http://127.0.0.1:3000",
        "http://localhost:3002",
        env.GetString("ADMIN_CONSOLE_URL"),
      ],
      allowMethods: ["POST", "GET", "PATCH", "DELETE", "PUT"],
      maxAge: 600,
      credentials: true,
    }),
  );
  app.use(
    timeout(
      3 * 60 * 1000,
      () =>
        new HTTPException(408, {
          message: `Request timeout after waiting 3 minutes. Please try again later.`,
        }),
    ),
  );

  const pool = createDBPool(cfg.datastore);
  const memberPool = createDBPool(cfg.memberDatastore);
  await migratePSQL(cfg.datastore);

  const redis = createRedisDB(cfg.redisOptions);

  app.use(async (ctx: Context, next: Next) => {
    ctx.set("datastore", pool);
    ctx.set("memberDatastore", memberPool);
    ctx.set("redisDB", redis);

    await next();
  });

  // Initial Updot Login (Background)
  UpdotAuthManager.getInstance()
    .getOrLogin()
    .catch((err) => {
      console.error("[Startup] Updot Initial Auth Failed:", err);
    });

  // Global error handler — always return JSON so clients can parse errors
  app.onError((err, ctx) => {
    const message = err instanceof Error ? err.message : "Internal server error";
    return ctx.json({ success: false, message, data: null }, 500);
  });

  app.route("", AppRoutes);

  const httpServer = serve({
    fetch: app.fetch,
    hostname: cfg.host,
    port: cfg.port,
  });
  await migratePSQL(cfg.datastore);

  await initializeBackgroundServices(pool, memberPool, httpServer);
  logInfo("Server booted up successfully");
}
