import {spawn} from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

import type {ClientConnectionSettings} from '@shared/schema/ClientConnectionSettings';
import type {Config} from '@shared/schema/Config';
import {configSchema} from '@shared/schema/Config';
import yargs from 'yargs/yargs';

import {version} from './package.json';

const {argv: argvObj} = yargs(process.argv.slice(2))
  .env('FLOOD_OPTION_')
  .option('baseuri', {
    default: '/',
    describe: "This URI will prefix all of Flood's HTTP requests",
    type: 'string',
  })
  .option('rundir', {
    alias: 'd',
    default: path.join(os.homedir(), '.local/share/flood'),
    describe: "Where to store Flood's runtime files (eg. database)",
    type: 'string',
  })
  .option('host', {
    alias: 'h',
    default: '127.0.0.1',
    describe: 'The host that Flood should listen for web connections on',
    type: 'string',
  })
  .option('port', {
    alias: 'p',
    default: 3000,
    describe: 'The port that Flood should listen for web connections on',
    type: 'string',
  })
  .option('secret', {
    alias: 's',
    hidden: true,
    describe: 'A unique secret, a random one will be generated if not provided',
    type: 'string',
  })
  .option('auth', {
    describe: 'Access control and user management method',
    choices: ['default', 'none'],
  })
  .option('noauth', {
    alias: 'n',
    hidden: true,
    default: false,
    describe: "Disable Flood's builtin access control system, deprecated, use auth=none instead",
    type: 'boolean',
  })
  .option('disable-rate-limit', {
    default: false,
    describe: 'disable api request limit except for login',
    hidden: true,
    type: 'boolean',
  })
  .option('dehost', {
    describe: 'Host of Deluge RPC interface',
    type: 'string',
  })
  .option('deport', {
    describe: 'Port of Deluge RPC interface',
    type: 'number',
  })
  .option('deuser', {
    describe: 'Username of Deluge RPC interface',
    type: 'string',
  })
  .option('depass', {
    describe: 'Password of Deluge RPC interface',
    type: 'string',
  })
  .option('rthost', {
    describe: "Host of rTorrent's SCGI interface",
    type: 'string',
  })
  .option('rtport', {
    describe: "Port of rTorrent's SCGI interface",
    type: 'number',
  })
  .option('rtsocket', {
    conflicts: ['rthost', 'rtport'],
    describe: "Path to rTorrent's SCGI unix socket",
    type: 'string',
  })
  .option('qburl', {
    describe: 'URL to qBittorrent Web API',
    type: 'string',
  })
  .option('qbuser', {
    describe: 'Username of qBittorrent Web API',
    type: 'string',
  })
  .option('qbpass', {
    describe: 'Password of qBittorrent Web API',
    type: 'string',
  })
  .option('trurl', {
    describe: 'URL to Transmission RPC interface',
    type: 'string',
  })
  .option('truser', {
    describe: 'Username of Transmission RPC interface',
    type: 'string',
  })
  .option('trpass', {
    describe: 'Password of Transmission RPC interface',
    type: 'string',
  })
  .group(
    [
      'dehost',
      'deport',
      'deuser',
      'depass',
      'rthost',
      'rtport',
      'rtsocket',
      'qburl',
      'qbuser',
      'qbpass',
      'trurl',
      'truser',
      'trpass',
    ],
    'When auth=none:',
  )
  .option('ssl', {
    default: false,
    describe: 'Enable SSL, key.pem and fullchain.pem needed in runtime directory',
    type: 'boolean',
  })
  .option('sslkey', {
    describe: 'Depends on ssl: Absolute path to private key for SSL',
    implies: 'ssl',
    hidden: true,
    type: 'string',
  })
  .option('sslcert', {
    describe: 'Depends on ssl: Absolute path to fullchain cert for SSL',
    implies: 'ssl',
    hidden: true,
    type: 'string',
  })
  .option('allowedpath', {
    describe: 'Allowed path for file operations, can be called multiple times',
    type: 'string',
  })
  .option('assets', {
    default: true,
    describe: 'ADVANCED: Serve static assets',
    hidden: true,
    type: 'boolean',
  })
  .option('dbclean', {
    default: 1000 * 60 * 60,
    describe: 'ADVANCED: Interval between database purge',
    hidden: true,
    type: 'number',
  })
  .option('maxhistorystates', {
    default: 30,
    describe: 'ADVANCED: Number of records of torrent download and upload speeds',
    hidden: true,
    type: 'number',
  })
  .option('clientpoll', {
    default: 1000 * 2,
    describe: 'ADVANCED: How often (in ms) Flood will request the torrent list',
    hidden: true,
    type: 'number',
  })
  .option('clientpollidle', {
    default: 1000 * 60 * 15,
    describe: 'ADVANCED: How often (in ms) Flood will request the torrent list when no user is present',
    hidden: true,
    type: 'number',
  })
  .option('rtorrent', {
    default: false,
    describe: 'ADVANCED: rTorrent daemon managed by Flood',
    hidden: true,
    type: 'boolean',
  })
  .option('rtconfig', {
    describe: 'ADVANCED: rtorrent.rc for managed rTorrent daemon',
    implies: 'rtorrent',
    hidden: true,
    type: 'string',
  })
  .option('test', {
    default: false,
    describe: 'DEV ONLY: Test setup',
    hidden: true,
    type: 'boolean',
  })
  .version(version)
  .alias('v', 'version')
  .help();

// HACK: pending yargs/yargs#2175

const argv = argvObj as Record<string, any>;

process.on('SIGINT', () => {
  process.exit();
});

try {
  fs.mkdirSync(path.join(argv.rundir), {recursive: true, mode: 0o700});
  fs.mkdirSync(path.join(argv.rundir, 'db'), {recursive: true});
  fs.mkdirSync(path.join(argv.rundir, 'temp'), {recursive: true});
} catch {
  console.error('Failed to access runtime directory');
  process.exit(1);
}

if (argv.rtorrent) {
  const args = [];
  let opts = 'system.daemon.set=true';

  if (typeof argv.rtconfig === 'string' && argv.rtconfig.length > 0) {
    args.push('-n');
    opts += `,import=${argv.rtconfig}`;
  }

  const rTorrentProcess = spawn('rtorrent', args.concat(['-o', opts]), {stdio: 'inherit'});

  fs.writeFileSync(path.join(argv.rundir, 'rtorrent.pid'), `${rTorrentProcess.pid}`);

  if (!argv.test) {
    rTorrentProcess.on('close', () => {
      process.exit(1);
    });
    rTorrentProcess.on('error', () => {
      process.exit(1);
    });
  }

  process.on('exit', () => {
    console.log('Killing rTorrent daemon...');
    rTorrentProcess.kill('SIGHUP');
  });
}

const DEFAULT_SECRET_PATH = path.join(argv.rundir, 'flood.secret');
let secret: string;

if (!argv.secret) {
  try {
    if (fs.existsSync(DEFAULT_SECRET_PATH)) {
      secret = fs.readFileSync(DEFAULT_SECRET_PATH, {encoding: 'utf8'});
    } else {
      const buf = Buffer.alloc(36);
      crypto.randomFillSync(buf);
      secret = buf.toString('hex');
      fs.writeFileSync(DEFAULT_SECRET_PATH, secret, {mode: 0o600});
    }
  } catch {
    console.error('Failed to read or generate secret');
    process.exit(1);
  }
} else {
  ({secret} = argv);
}

let connectionSettings: Partial<ClientConnectionSettings> | undefined;
if (argv.rtsocket != null || argv.rthost != null) {
  if (argv.rtsocket != null) {
    connectionSettings = {
      client: 'rTorrent',
      type: 'socket',
      version: 1,
      socket: argv.rtsocket,
    };
  } else {
    connectionSettings = {
      client: 'rTorrent',
      type: 'tcp',
      version: 1,
      host: argv.rthost,
      port: argv.rtport,
    };
  }
} else if (argv.qburl != null) {
  connectionSettings = {
    client: 'qBittorrent',
    type: 'web',
    version: 1,
    url: argv.qburl,
    username: argv.qbuser,
    password: argv.qbpass,
  };
} else if (argv.trurl != null) {
  connectionSettings = {
    client: 'Transmission',
    type: 'rpc',
    version: 1,
    url: argv.trurl,
    username: argv.truser,
    password: argv.trpass,
  };
} else if (argv.dehost != null) {
  connectionSettings = {
    client: 'Deluge',
    type: 'rpc',
    version: 1,
    host: argv.dehost,
    port: argv.deport,
    username: argv.deuser,
    password: argv.depass,
  };
}

let authMethod: Config['authMethod'] = 'default';
if (argv.noauth || argv.auth === 'none') {
  authMethod = 'none';
}

let allowedPaths: string[] = [];
if (typeof argv.allowedpath === 'string') {
  allowedPaths = allowedPaths.concat(argv.allowedpath.split(','));
} else if (Array.isArray(argv.allowedpath)) {
  allowedPaths = allowedPaths.concat(argv.allowedpath);
}

const result = configSchema.safeParse({
  baseURI: argv.baseuri,
  dbCleanInterval: argv.dbclean,
  dbPath: path.resolve(path.join(argv.rundir, 'db')),
  tempPath: path.resolve(path.join(argv.rundir, 'temp')),
  authMethod,
  configUser: connectionSettings,
  floodServerHost: argv.host,
  floodServerPort: argv.port,
  maxHistoryStates: argv.maxhistorystates,
  torrentClientPollInterval: argv.clientpoll,
  torrentClientPollIntervalIdle: argv.clientpollidle,
  secret,
  ssl: argv.ssl,
  sslKey: argv.sslkey || path.resolve(path.join(argv.rundir, 'key.pem')),
  sslCert: argv.sslcert || path.resolve(path.join(argv.rundir, 'fullchain.pem')),
  allowedPaths: allowedPaths.length > 0 ? allowedPaths : undefined,
  serveAssets: argv.assets,
  disableRateLimit: argv.disableRateLimit,
});

if (!result.success) {
  console.error(`Invalid configuration: ${result.error.message}`);
  process.exit(1);
}

export default result.data;
