#!/usr/bin/env node

import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import { pathToFileURL } from "node:url";
import {
  getConfigPath,
  readConfig,
  writeConfig,
} from "../host-service/config.mjs";
import {
  hashDashboardPassword,
  verifyDashboardPassword,
} from "../host-service/dashboard-password.mjs";

const DEFAULT_VERIFICATION_DASHBOARD_USER = "admin";
const DEFAULT_VERIFICATION_DASHBOARD_PASSWORD = "oysterun2026";
const REDACTED = "[redacted]";

function requiredEnv(name) {
  const value = process.env[name];
  if (!value || !value.trim()) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value.trim();
}

function optionalEnv(name) {
  const value = process.env[name];
  return value && value.trim() ? value.trim() : null;
}

function normalizeStackName(stackName) {
  if (typeof stackName !== "string" || !stackName.trim()) {
    throw new Error("stack_dashboard_credentials_missing: stack name required");
  }
  return stackName.trim();
}

function isProductionStack(stackName) {
  return stackName === "production";
}

function isDefaultVerificationStack(stackName) {
  return /^test[1-4]$/.test(stackName) || /^test_w[0-9]+$/.test(stackName);
}

function readRawConfigKeys(configPath) {
  if (!existsSync(configPath)) return [];
  const parsed = JSON.parse(readFileSync(configPath, "utf8"));
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error(`Invalid config format: ${configPath}`);
  }
  return Object.keys(parsed).sort();
}

function nonCredentialFieldMap(config) {
  const result = {};
  for (const [key, value] of Object.entries(config)) {
    if (key === "dashboard_user" || key === "dashboard_password_hash") continue;
    result[key] = JSON.stringify(value);
  }
  return result;
}

function nonCredentialFieldsPreserved(before, after) {
  const beforeMap = nonCredentialFieldMap(before);
  for (const [key, value] of Object.entries(beforeMap)) {
    if (JSON.stringify(after[key]) !== value) {
      return false;
    }
  }
  return true;
}

export function resolveDashboardCredentialSource(stackName, env = process.env) {
  const normalizedStackName = normalizeStackName(stackName);
  const explicitUser = env.OYSTERUN_STACK_DASHBOARD_USER?.trim() || null;
  const explicitPassword = env.OYSTERUN_STACK_DASHBOARD_PASSWORD || null;
  if (explicitUser || explicitPassword) {
    if (!explicitUser || !explicitPassword || !explicitPassword.trim()) {
      throw new Error("stack_dashboard_credentials_missing: incomplete explicit dashboard credential source");
    }
    return {
      kind: "explicit_stack_dashboard_env",
      user: explicitUser,
      password: explicitPassword,
      redacted: true,
    };
  }
  if (isProductionStack(normalizedStackName)) {
    return {
      kind: "production_existing_config_only",
      user: null,
      password: null,
      redacted: true,
    };
  }
  if (isDefaultVerificationStack(normalizedStackName)) {
    return {
      kind: "current_team_verification_dashboard_credentials",
      user: DEFAULT_VERIFICATION_DASHBOARD_USER,
      password: DEFAULT_VERIFICATION_DASHBOARD_PASSWORD,
      redacted: true,
    };
  }
  throw new Error("stack_dashboard_credentials_missing: explicit stack dashboard credentials required for this stack");
}

function baseProof({
  stackName,
  hostOrigin,
  configDir,
  configPath,
  credentialSourceKind,
  existingStackCredentialsPreserved,
  stackConfigExistingFieldsPreserved,
  dashboardPasswordHashVerifies,
  authLoginStatus = "not_checked",
  authLoginAccepted = false,
}) {
  const config = readConfig();
  return {
    stack_name: stackName,
    host_origin: hostOrigin,
    config_dir: configDir,
    config_path: configPath,
    dashboard_user_set: Boolean(config.dashboard_user),
    dashboard_password_hash_set: Boolean(config.dashboard_password_hash),
    dashboard_password_plaintext_recorded: false,
    dashboard_password_hash_verifies: dashboardPasswordHashVerifies,
    credential_source_kind: credentialSourceKind,
    credential_source_redacted: true,
    existing_stack_credentials_preserved: existingStackCredentialsPreserved,
    stack_config_existing_fields_preserved: stackConfigExistingFieldsPreserved,
    auth_login_status: authLoginStatus,
    auth_login_accepted: authLoginAccepted,
    token_redacted: true,
    cookie_redacted: true,
  };
}

function writeProofFile(path, proof) {
  if (!path) return;
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, `${JSON.stringify(proof, null, 2)}\n`);
}

export function ensureStackDashboardCredentials({
  stackName,
  hostOrigin,
  configDir,
  proofPath = null,
} = {}) {
  const normalizedStackName = normalizeStackName(stackName);
  const configPath = getConfigPath();
  const rawKeysBefore = readRawConfigKeys(configPath);
  const beforeConfig = readConfig();
  const hadCompleteCredentials = Boolean(beforeConfig.dashboard_user && beforeConfig.dashboard_password_hash);
  const source = hadCompleteCredentials
    ? {
        kind: "existing_stack_config",
        user: beforeConfig.dashboard_user,
        password: null,
        redacted: true,
      }
    : resolveDashboardCredentialSource(normalizedStackName);

  let afterConfig = beforeConfig;
  if (!hadCompleteCredentials) {
    if (source.kind === "production_existing_config_only") {
      const proof = baseProof({
        stackName: normalizedStackName,
        hostOrigin,
        configDir,
        configPath,
        credentialSourceKind: source.kind,
        existingStackCredentialsPreserved: false,
        stackConfigExistingFieldsPreserved: true,
        dashboardPasswordHashVerifies: false,
      });
      writeProofFile(proofPath, proof);
      return proof;
    }
    afterConfig = writeConfig({
      dashboard_user: source.user,
      dashboard_password_hash: hashDashboardPassword(source.password),
    });
  }

  const rawKeysAfter = readRawConfigKeys(configPath);
  const stackConfigExistingFieldsPreserved = rawKeysBefore.every((key) => rawKeysAfter.includes(key))
    && nonCredentialFieldsPreserved(beforeConfig, afterConfig);
  const passwordForVerification = source.password;
  const dashboardPasswordHashVerifies = hadCompleteCredentials || Boolean(
    passwordForVerification
      && afterConfig.dashboard_user === source.user
      && afterConfig.dashboard_password_hash
      && verifyDashboardPassword(passwordForVerification, afterConfig.dashboard_password_hash)
  );
  const proof = baseProof({
    stackName: normalizedStackName,
    hostOrigin,
    configDir,
    configPath,
    credentialSourceKind: hadCompleteCredentials
      ? "existing_stack_config"
      : source.kind,
    existingStackCredentialsPreserved: hadCompleteCredentials,
    stackConfigExistingFieldsPreserved,
    dashboardPasswordHashVerifies,
  });
  writeProofFile(proofPath, proof);
  return proof;
}

export async function probeStackDashboardAuth({
  stackName,
  hostOrigin,
  configDir,
  proofPath = null,
} = {}) {
  const normalizedStackName = normalizeStackName(stackName);
  const configPath = getConfigPath();
  const config = readConfig();
  const hasCompleteCredentials = Boolean(
    config.dashboard_user && config.dashboard_password_hash
  );
  const source = hasCompleteCredentials
    ? {
        kind: "existing_stack_config",
        user: config.dashboard_user,
        password: null,
        redacted: true,
      }
    : resolveDashboardCredentialSource(normalizedStackName);
  const sourceCanVerify = Boolean(source.user && source.password);
  const hashVerifies = sourceCanVerify
    && Boolean(config.dashboard_password_hash)
    && verifyDashboardPassword(source.password, config.dashboard_password_hash);
  let authLoginStatus = "not_checked";
  let authLoginAccepted = false;

  if (sourceCanVerify && hashVerifies && hostOrigin) {
    const response = await fetch(`${hostOrigin.replace(/\/+$/, "")}/auth/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        username: source.user,
        password: source.password,
      }),
    });
    authLoginStatus = String(response.status);
    if (response.ok) {
      const body = await response.json();
      authLoginAccepted = typeof body.token === "string" && body.token.length > 0
        && Boolean(response.headers.get("set-cookie"));
    }
  }

  const proof = baseProof({
    stackName: normalizedStackName,
    hostOrigin,
    configDir,
    configPath,
    credentialSourceKind: sourceCanVerify
      ? source.kind
      : "existing_stack_config",
    existingStackCredentialsPreserved: Boolean(config.dashboard_user && config.dashboard_password_hash),
    stackConfigExistingFieldsPreserved: true,
    dashboardPasswordHashVerifies: hashVerifies || Boolean(config.dashboard_user && config.dashboard_password_hash && !sourceCanVerify),
    authLoginStatus,
    authLoginAccepted,
  });
  writeProofFile(proofPath, proof);
  return proof;
}

async function main() {
  const [command] = process.argv.slice(2);
  const options = {
    stackName: requiredEnv("OYSTERUN_STACK_NAME"),
    hostOrigin: optionalEnv("OYSTERUN_HOST_ORIGIN"),
    configDir: requiredEnv("OYSTERUN_CONFIG_DIR"),
    proofPath: optionalEnv("OYSTERUN_STACK_DASHBOARD_CREDENTIALS_PROOF_PATH"),
  };
  if (command === "ensure") {
    const proof = ensureStackDashboardCredentials(options);
    process.stdout.write(`${JSON.stringify({
      stack_name: proof.stack_name,
      dashboard_user_set: proof.dashboard_user_set,
      dashboard_password_hash_set: proof.dashboard_password_hash_set,
      dashboard_password_plaintext_recorded: false,
      credential_source_kind: proof.credential_source_kind,
      credential_source_redacted: true,
    })}\n`);
  } else if (command === "probe-auth") {
    const proof = await probeStackDashboardAuth(options);
    process.stdout.write(`${JSON.stringify({
      stack_name: proof.stack_name,
      auth_login_status: proof.auth_login_status,
      auth_login_accepted: proof.auth_login_accepted,
      token_redacted: true,
      cookie_redacted: true,
    })}\n`);
  } else {
    throw new Error("Usage: stack_dashboard_credentials.mjs ensure | probe-auth");
  }
}

if (import.meta.url === pathToFileURL(process.argv[1] || "").href) {
  main().catch((error) => {
    process.stderr.write(`[stack-dashboard-credentials] ${error instanceof Error ? error.message : String(error)}\n`);
    process.exit(1);
  });
}
