import { Elysia, t, type Context } from 'elysia'
import { version } from './package.json';
import { basicAuth } from '@eelkevdbos/elysia-basic-auth'
import net from 'net'
import { appendFile } from "node:fs/promises";

if (!process.env.TRACKER_HELPER_USERNAME) throw new Error('TRACKER_HELPER_USERNAME missing in env');
if (!process.env.TRACKER_HELPER_PASSWORD) throw new Error('TRACKER_HELPER_PASSWORD missing in env');

const accesslistFilePath = process.env.TRACKER_HELPER_ACCESSLIST_PATH || "/var/lib/aquatic/accesslist"
const username = process.env.TRACKER_HELPER_USERNAME!
const password = process.env.TRACKER_HELPER_PASSWORD!

interface DockerContainer {
  Id: string;
  Command: string;
}

const authOpts = {
  scope: [
    "/accesslist",
    "/version"
  ],
  credentials: [
    {
      username: username,
      password: password
    }
  ]
}

const startupChecks = async function startupChecks() {


  if (!process.env.TRACKER_HELPER_ACCESSLIST_PATH) {
    console.warn(`TRACKER_HELPER_ACCESSLIST_PATH is missing in env. Using default ${accesslistFilePath}`)
  }

}




const getAccesslist = async function getAccesslist(ctx: Context) {
  const wl = Bun.file(accesslistFilePath); // relative to cwd
  console.debug(`read from accesslist file at ${accesslistFilePath}. size=${wl.size}, type=${wl.type}`)
  return wl.text()
}




async function findOpentrackerContainer(socketPath = "/var/run/docker.sock"): Promise<DockerContainer | null> {
  return new Promise((resolve, reject) => {
    console.debug(`opening net client at socketPath=${socketPath}`)
    const client = net.createConnection(socketPath, () => {
      const request = 'GET /containers/json HTTP/1.0\r\n\r\n';
      client.write(request);
    });

    console.debug(`waiting for response from socket`)
    let response = '';
    client.on('data', (data) => {
      console.debug(`client got data`)
      response += data.toString();
    });

    console.debug(`waiting for connection end`)
    client.on('end', () => {
      console.debug(`client end detected`)
      try {
        const body = response.split('\r\n\r\n')[1];
        const containers: DockerContainer[] = JSON.parse(body);
        const container = containers.find(c => c.Command.includes('/bin/opentracker'));
        resolve(container || null);
      } catch (err) {
        reject(err);
      }
    });

    client.on('error', (err) => {
      console.error(`net client encountered error ${err}`)
      reject(err);
    });
  });
}


async function killContainer(socketPath = "/var/run/docker.sock", containerId: string, signal = "SIGTERM") {

  const request = `POST /containers/${containerId}/kill?signal=${signal} HTTP/1.0\r\n\r\n`;

  return new Promise((resolve, reject) => {
    const client = net.createConnection(socketPath, () => {
      client.write(request);
    });

    client.on('data', (data: any) => {
      // console.log(data.toString());
      client.end();
      resolve(data.toString());
    });

    client.on('error', (err: any) => {
      console.error('Error:', err);
      reject(err);
    });
  });
}

const maybeKillContainer = async function maybeKillContainer(signal: string = "SIGUSR1") {

  const sockFile = Bun.file('/var/run/docker.sock')
  const sockFileExists = await sockFile.exists()
  if (!sockFileExists) {
    console.warn("⚠️ docker sock file not found. skipping.")
  } else {
    console.debug('looking for opentracker container')
    const container = await findOpentrackerContainer()
    if (!container) {
      console.warn('⚠️ failed to find opentracker container');
    } else {
      await killContainer(undefined, container.Id, signal)
      console.debug('sending SIGUSR1 to container ' + container.Id)
    }
  }
}


const postAccesslist = async function postAccesslist(ctx: Context) {
  let body = ctx.body

  console.debug('appending to accesslist at ' + accesslistFilePath)

  try {
    await appendFile(accesslistFilePath, body + "\n");

    await maybeKillContainer("SIGUSR1")

    ctx.set.status = 201
  } catch (e) {
    console.error('During postAccesslist, the following error was caught.')
    console.error(e)
    throw e
  }

  return body
}

await startupChecks()


const app = new Elysia()
  .use(basicAuth(authOpts))
  .get('/health', () => 'OK')
  .get('/version', () => `version ${version}`)
  .get('/accesslist', getAccesslist)
  .post('/accesslist', postAccesslist, {
    body: t.String()
  })


export default app