import { join } from "node:path";
import { exit, versions } from "node:process";

import { createHonoServer } from "react-router-hono-server/node";

import log from "~/utils/log";

import { loadIntegration } from "./config/integration";
import { loadConfig } from "./config/load";
import { createDbClient } from "./db/client.server";
import { createHeadscaleInterface } from "./headscale/api";
import { loadHeadscaleConfig } from "./headscale/config-loader";
import { createLiveStore, nodesResource, usersResource } from "./headscale/live-store";
import { createHeadplaneAgent } from "./hp-agent";
import { createAuthService } from "./web/auth";

declare global {
  const __PREFIX__: string;
  const __VERSION__: string;
}

// MARK: Side-Effects
// This module contains a side-effect because everything running here
// exists for the lifetime of the process, making it appropriate.
log.info("server", "Running Node.js %s", versions.node);
let config: HeadplaneConfig;

try {
  config = await loadConfig();
} catch (error) {
  if (error instanceof ConfigError) {
    log.error("server", "Unable to load configuration: %s", error.message);
  }

  exit(1);
}

const db = await createDbClient(join(config.server.data_path, "hp_persist.db"));
const agents = await createHeadplaneAgent(config.integration?.agent, config.headscale.url, db);

const hsApi = await createHeadscaleInterface(config.headscale.url, config.headscale.tls_cert_path);

// We also use this file to load anything needed by the react router code.
// These are usually per-request things that we need access to, like the
// helper that can issue and revoke cookies.
export type LoadContext = typeof appLoadContext;

import "react-router";
import { HeadplaneConfig } from "./config/config-schema";
import { ConfigError } from "./config/error";
import { createLazyOidcConnector } from "./web/oidc-connector";

declare module "react-router" {
  interface AppLoadContext extends LoadContext {}
}

const hsLive = createLiveStore([nodesResource, usersResource]);

const appLoadContext = {
  config,
  hsLive,
  hs: await loadHeadscaleConfig(
    config.headscale.config_path,
    config.headscale.config_strict,
    config.headscale.dns_records_path,
  ),

  auth: createAuthService({
    secret: config.server.cookie_secret,
    db,
    cookie: {
      name: "_hp_auth",
      secure: config.server.cookie_secure,
      maxAge: config.server.cookie_max_age,
      domain: config.server.cookie_domain,
    },
  }),

  hsApi,
  agents,
  integration: await loadIntegration(config.integration),
  oidc:
    config.oidc && config.oidc.enabled !== false
      ? {
          apiKey: config.oidc.headscale_api_key,
          connector: createLazyOidcConnector(
            config.server.base_url,
            config.oidc,
            hsApi.getRuntimeClient(config.oidc.headscale_api_key),
          ),
        }
      : undefined,
  db,
};

declare module "react-router" {
  interface AppLoadContext extends LoadContext {}
}

export default createHonoServer({
  overrideGlobalObjects: true,
  port: config.server.port,
  hostname: config.server.host,
  beforeAll: async (app) => {
    app.use(__PREFIX__, async (c) => {
      return c.redirect(`${__PREFIX__}/`);
    });
  },
  serveStaticOptions: {
    publicAssets: {
      // This is part of our monkey-patch for react-router-hono-server
      // To see the first part, go to the patches/ directory.
      rewriteRequestPath: (path) => path.replace(`${__PREFIX__}`, ""),
    },
    clientAssets: {
      // This is part of our monkey-patch for react-router-hono-server
      // To see the first part, go to the patches/ directory.
      rewriteRequestPath: (path) => path.replace(`${__PREFIX__}`, ""),
    },
  },

  // Only log in development mode
  defaultLogger: import.meta.env.DEV,
  getLoadContext() {
    // TODO: This is the place where we can handle reverse proxy translation
    // This is better than doing it in the OIDC client, since we can do it
    // for all requests, not just OIDC ones.
    return appLoadContext;
  },

  listeningListener(info) {
    log.info("server", "Running on %s:%s", info.address, info.port);
  },
});

// Prune expired auth sessions every 15 minutes
setInterval(
  () => {
    appLoadContext.auth.pruneExpiredSessions();
  },
  15 * 60 * 1000,
);

process.on("SIGINT", () => {
  log.info("server", "Received SIGINT, shutting down...");
  process.exit(0);
});

process.on("SIGTERM", () => {
  log.info("server", "Received SIGTERM, shutting down...");
  process.exit(0);
});
