import { env as processEnv } from "node:process";
import type { StructuredItem } from "./types.ts";
import { loadFixtureItems } from "./fixtures.ts";
import { listValue, loadEnv, scalar } from "./fs.ts";
import { splitEnvAnyGroup } from "./env-groups.ts";

type EnvSource = Record<string, string | undefined>;

export type EnvReadiness = {
  status: "ready" | "missing" | "not_required";
  required: string[];
  configured: string[];
  missing: string[];
  values: Record<string, string>;
};

export type AutomationReadiness = EnvReadiness & {
  script: string;
  defaulted: string[];
  pipeline_env_required: boolean;
  env_aliases: Array<{
    target: string;
    source: string;
    configured: boolean;
  }>;
};

export type ManualReadiness = {
  status: "manual_check" | "not_required";
  preconditions: string[];
  setup: string[];
  cleanup: string[];
};

export type FixtureReadiness = {
  status: "ready" | "missing" | "not_required";
  required: Array<{
    id: string;
    kind: string;
    path: string;
    exists: boolean;
  }>;
  missing: string[];
};

const secretKeyRe = /(?:api[_-]?key|authorization|bearer|credential|jwt|oauth|password|secret|token)/i;

export function redactEnvValue(key: string, value: string): string {
  if (!value) return "";
  if (secretKeyRe.test(key)) return "[redacted]";
  return value.replace(/(https?:\/\/)([^:@/\s]+):([^@/\s]+)@/i, "$1[redacted]@");
}

export function runtimeEnv(root: string): Record<string, string> {
  const result: Record<string, string> = { ...loadEnv(root) };
  for (const [key, value] of Object.entries(processEnv)) {
    if (typeof value === "string") result[key] = value;
  }
  return result;
}

function envReadiness(
  keys: string[],
  env: EnvSource,
  defaults: Record<string, string> = {},
  anyGroups: string[] = [],
  providedBySetup: Set<string> = new Set(),
): EnvReadiness {
  const required = [...keys];
  const configured = required.filter((key) => Boolean(env[key]) || Boolean(defaults[key]) || providedBySetup.has(key));
  const missing = required.filter((key) => !env[key] && !defaults[key] && !providedBySetup.has(key));
  const values: Record<string, string> = Object.fromEntries(
    required.map((key) => [key, redactEnvValue(key, env[key] ?? defaults[key] ?? setupProvidedValue(key, providedBySetup))]),
  );

  for (const group of anyGroups) {
    const keysInGroup = splitEnvAnyGroup(group);
    required.push(group);
    const configuredKeys = keysInGroup.filter((key) => Boolean(env[key]) || Boolean(defaults[key]) || providedBySetup.has(key));
    if (configuredKeys.length === 0) missing.push(group);
    else configured.push(...configuredKeys);
    for (const key of keysInGroup) {
      values[key] = redactEnvValue(key, env[key] ?? defaults[key] ?? setupProvidedValue(key, providedBySetup));
    }
  }

  return {
    status: required.length === 0 ? "not_required" : missing.length === 0 ? "ready" : "missing",
    required,
    configured: Array.from(new Set(configured)),
    missing,
    values,
  };
}

function setupProvidedValue(key: string, providedBySetup: Set<string>): string {
  return providedBySetup.has(key) ? "[provided by setup_automation]" : "";
}

export function setupProvidedEnv(item: StructuredItem): Set<string> {
  return new Set(listValue(item.fields, "setup_provides_env"));
}

export function automationEnvDefaults(item: StructuredItem, env: EnvSource = processEnv): Record<string, string> {
  const mapping: Array<[string, string]> = [
    ["automation_prompt", "LANGBOT_E2E_PROMPT"],
    ["automation_prompts_json", "LANGBOT_E2E_PROMPTS_JSON"],
    ["automation_expected_text", "LANGBOT_E2E_EXPECTED_TEXT"],
    ["automation_response_timeout_ms", "LANGBOT_E2E_RESPONSE_TIMEOUT_MS"],
    ["automation_stream_output", "LANGBOT_E2E_STREAM_OUTPUT"],
    ["automation_image_base64_fixture", "LANGBOT_E2E_IMAGE_BASE64_PATH"],
    ["automation_runner_config_patch_json", "LANGBOT_E2E_RUNNER_CONFIG_PATCH_JSON"],
    ["automation_restore_runner_config", "LANGBOT_E2E_RESTORE_RUNNER_CONFIG"],
    ["automation_expected_runner_id", "LANGBOT_E2E_EXPECTED_RUNNER_ID"],
    ["automation_reset_debug_chat", "LANGBOT_E2E_RESET_DEBUG_CHAT"],
    ["automation_debug_chat_session_type", "LANGBOT_E2E_DEBUG_CHAT_SESSION_TYPE"],
    ["automation_debug_chat_response_p95_ms", "LANGBOT_E2E_DEBUG_CHAT_RESPONSE_P95_MS"],
    ["automation_debug_chat_max_error_rate", "LANGBOT_E2E_DEBUG_CHAT_MAX_ERROR_RATE"],
    ["automation_debug_chat_load_requests", "LANGBOT_DEBUG_CHAT_LOAD_REQUESTS"],
    ["automation_debug_chat_load_concurrency", "LANGBOT_DEBUG_CHAT_LOAD_CONCURRENCY"],
    ["automation_debug_chat_load_timeout_ms", "LANGBOT_DEBUG_CHAT_LOAD_TIMEOUT_MS"],
    ["automation_debug_chat_load_response_p95_ms", "LANGBOT_DEBUG_CHAT_LOAD_RESPONSE_P95_MS"],
    ["automation_debug_chat_load_first_response_p95_ms", "LANGBOT_DEBUG_CHAT_LOAD_FIRST_RESPONSE_P95_MS"],
    ["automation_debug_chat_load_max_error_rate", "LANGBOT_DEBUG_CHAT_LOAD_MAX_ERROR_RATE"],
    ["automation_debug_chat_load_min_error_rate", "LANGBOT_DEBUG_CHAT_LOAD_MIN_ERROR_RATE"],
    ["automation_debug_chat_load_min_error_count", "LANGBOT_DEBUG_CHAT_LOAD_MIN_ERROR_COUNT"],
    ["automation_debug_chat_load_min_ok_count", "LANGBOT_DEBUG_CHAT_LOAD_MIN_OK_COUNT"],
    ["automation_debug_chat_load_min_provider_fault_count", "LANGBOT_DEBUG_CHAT_LOAD_MIN_PROVIDER_FAULT_COUNT"],
    ["automation_debug_chat_load_expected_prefix", "LANGBOT_DEBUG_CHAT_LOAD_EXPECTED_PREFIX"],
    ["automation_debug_chat_load_prompt_template", "LANGBOT_DEBUG_CHAT_LOAD_PROMPT_TEMPLATE"],
    ["automation_debug_chat_load_stream", "LANGBOT_DEBUG_CHAT_LOAD_STREAM"],
    ["automation_debug_chat_load_reset", "LANGBOT_DEBUG_CHAT_LOAD_RESET"],
    ["automation_debug_chat_load_fail_on_final_mismatch", "LANGBOT_DEBUG_CHAT_LOAD_FAIL_ON_FINAL_MISMATCH"],
    ["automation_fake_provider_response_text", "LANGBOT_FAKE_PROVIDER_RESPONSE_TEXT"],
    ["automation_fake_provider_first_token_delay_ms", "LANGBOT_FAKE_PROVIDER_FIRST_TOKEN_DELAY_MS"],
    ["automation_fake_provider_chunk_delay_ms", "LANGBOT_FAKE_PROVIDER_CHUNK_DELAY_MS"],
    ["automation_fake_provider_chunk_count", "LANGBOT_FAKE_PROVIDER_CHUNK_COUNT"],
    ["automation_fake_provider_fail_first_n", "LANGBOT_FAKE_PROVIDER_FAIL_FIRST_N"],
    ["automation_fake_provider_fail_every_n", "LANGBOT_FAKE_PROVIDER_FAIL_EVERY_N"],
    ["automation_fake_provider_fault_status", "LANGBOT_FAKE_PROVIDER_FAULT_STATUS"],
    ["automation_fake_provider_fail_after_first_chunk", "LANGBOT_FAKE_PROVIDER_FAIL_AFTER_FIRST_CHUNK"],
    ["automation_fake_provider_dynamic_response", "LANGBOT_FAKE_PROVIDER_DYNAMIC_RESPONSE"],
    ["automation_filesystem_checks_json", "LANGBOT_E2E_FILESYSTEM_CHECKS_JSON"],
    ["automation_plugin_package", "LANGBOT_E2E_PLUGIN_PACKAGE"],
    ["automation_expected_plugin_id", "LANGBOT_E2E_EXPECTED_PLUGIN_ID"],
    ["automation_expected_tool", "LANGBOT_E2E_EXPECTED_TOOL"],
  ];
  const defaults: Record<string, string> = {};
  for (const [field, envKey] of mapping) {
    const value = scalar(item.fields, field);
    if (value) defaults[envKey] = expandEnvRefs(value, env);
  }
  const failurePatterns = listValue(item.fields, "failure_patterns");
  if (failurePatterns.length > 0) defaults.LANGBOT_E2E_FAILURE_SIGNALS = failurePatterns.join("\n");
  return defaults;
}

function expandEnvRefs(value: string, env: EnvSource): string {
  return value.replace(/\$\{([A-Z][A-Z0-9_]*)\}|\$([A-Z][A-Z0-9_]*)/g, (_match, braced, bare) => {
    return env[braced || bare] || "";
  });
}

export function caseEnvReadiness(item: StructuredItem, env: EnvSource): EnvReadiness {
  const aliasSources = new Set(automationEnvAliases(item, env).map((alias) => alias.source));
  const provided = setupProvidedEnv(item);
  return envReadiness(
    listValue(item.fields, "env").filter((key) => !aliasSources.has(key)),
    env,
    {},
    listValue(item.fields, "env_any"),
    provided,
  );
}

function automationEnvAliases(item: StructuredItem, env: EnvSource): Array<{
  target: string;
  source: string;
  configured: boolean;
}> {
  const provided = setupProvidedEnv(item);
  const mapping: Array<[string, string]> = [
    ["automation_pipeline_url_env", "LANGBOT_E2E_PIPELINE_URL"],
    ["automation_pipeline_name_env", "LANGBOT_E2E_PIPELINE_NAME"],
  ];
  return mapping
    .map(([field, target]) => {
      const source = scalar(item.fields, field);
      return source ? { target, source, configured: Boolean(env[source]) || provided.has(source) } : null;
    })
    .filter((item): item is { target: string; source: string; configured: boolean } => item !== null);
}

export function automationPipelineEnvRequired(item: StructuredItem): boolean {
  return Boolean(scalar(item.fields, "automation_pipeline_url_env") || scalar(item.fields, "automation_pipeline_name_env"));
}

export function caseAutomationReadiness(item: StructuredItem, env: EnvSource): AutomationReadiness {
  const script = scalar(item.fields, "automation");
  const aliases = automationEnvAliases(item, env);
  const aliasSources = new Set(aliases.map((alias) => alias.source));
  const defaults = automationEnvDefaults(item, env);
  const provided = setupProvidedEnv(item);
  const requiredKeys = listValue(item.fields, "automation_env").filter((key) => !aliasSources.has(key));
  const readiness = envReadiness(requiredKeys, env, defaults, listValue(item.fields, "automation_env_any"), provided);
  const defaulted = requiredKeys.filter((key) => !env[key] && Boolean(defaults[key]));
  const aliasConfigured = aliases.some((alias) => alias.configured);
  const aliasMissing = automationPipelineEnvRequired(item) && !aliasConfigured
    ? [aliases.map((alias) => alias.source).join("|")]
    : [];
  const missing = [...readiness.missing, ...aliasMissing].filter(Boolean);
  const configured = [
    ...readiness.configured,
    ...aliases.filter((alias) => alias.configured).map((alias) => alias.source),
  ];
  const values = {
    ...readiness.values,
    ...Object.fromEntries(aliases.map((alias) => [
      alias.source,
      redactEnvValue(alias.source, env[alias.source] ?? setupProvidedValue(alias.source, provided)),
    ])),
  };
  return {
    ...readiness,
    status: script ? missing.length === 0 ? "ready" : "missing" : "not_required",
    script,
    defaulted,
    required: [...readiness.required, ...aliases.map((alias) => alias.source)],
    configured,
    missing,
    values,
    pipeline_env_required: automationPipelineEnvRequired(item),
    env_aliases: aliases,
  };
}

export function resolvedAutomationEnvOverrides(item: StructuredItem, env: EnvSource): Record<string, string> {
  const overrides: Record<string, string> = {};
  for (const alias of automationEnvAliases(item, env)) {
    const value = env[alias.source];
    if (value) overrides[alias.target] = value;
  }
  for (const [key, value] of Object.entries(automationEnvDefaults(item, env))) {
    overrides[key] = expandEnvRefs(value, env);
  }
  if (automationPipelineEnvRequired(item)) overrides.LANGBOT_E2E_PIPELINE_REQUIRED = "1";
  return overrides;
}

export function caseManualReadiness(item: StructuredItem): ManualReadiness {
  const preconditions = listValue(item.fields, "preconditions");
  const setup = listValue(item.fields, "setup");
  const cleanup = listValue(item.fields, "cleanup");
  return {
    status: preconditions.length > 0 || setup.length > 0 ? "manual_check" : "not_required",
    preconditions,
    setup,
    cleanup,
  };
}

export function caseFixtureReadiness(root: string, caseId: string): FixtureReadiness {
  const fixtures = loadFixtureItems(root).items
    .filter((item) => item.related_cases.includes(caseId))
    .map((item) => ({
      id: item.id,
      kind: item.kind,
      path: item.path,
      exists: item.exists,
    }));
  const missing = fixtures.filter((item) => !item.exists).map((item) => item.id);
  return {
    status: fixtures.length === 0 ? "not_required" : missing.length === 0 ? "ready" : "missing",
    required: fixtures,
    missing,
  };
}
