import fs from "fs";
import path from "path";
import { DEFAULT_FILE_TIMEOUT_SECONDS, normalizeExecutionConfig, normalizeRuntimeMaxConcurrentTasks, normalizeRuntimeInstances } from "../runner/execution-config.mjs";
import { normalizeKnownFailureIssueValidationConfig } from "../known-failures/github.mjs";
import { normalizeRuntimeToolchain } from "../toolchains/index.mjs";
import { resolveServiceCwd } from "./paths.mjs";

export function normalizeOptionalString(value) {
  if (typeof value !== "string") return null;
  const normalized = value.trim();
  return normalized.length > 0 ? normalized : null;
}

export function normalizeReportingConfig(value) {
  if (!value) return null;

  const knownFailuresFile = normalizeOptionalString(value.knownFailuresFile);
  if (!knownFailuresFile) {
    throw new Error('testkit.setup.ts reporting.knownFailuresFile must be a non-empty string');
  }

  const issueValidation = normalizeKnownFailureIssueValidationConfig(value.issueValidation);

  return {
    knownFailuresFile,
    issueValidation,
  };
}

export function inferLocalRuntime(productDir, cwd) {
  const absoluteCwd = resolveServiceCwd(productDir, cwd);
  if (!fs.existsSync(absoluteCwd)) return undefined;

  if (detectNextApp(absoluteCwd)) {
    return {
      cwd,
      start: "./node_modules/.bin/next dev -p {port}",
      port: 3000,
      baseUrl: "http://127.0.0.1:{port}",
      readyUrl: "http://127.0.0.1:{port}",
      env: {},
    };
  }

  if (fs.existsSync(path.join(absoluteCwd, "cmd", "server"))) {
    return {
      cwd,
      start: "go run ./cmd/server",
      port: 3000,
      baseUrl: "http://127.0.0.1:{port}",
      readyUrl: "http://127.0.0.1:{port}/health",
      env: {},
    };
  }

  if (fs.existsSync(path.join(absoluteCwd, "package.json")) && fs.existsSync(path.join(absoluteCwd, "src"))) {
    return {
      cwd,
      start: "./node_modules/.bin/tsx watch src/index.ts",
      port: 3000,
      baseUrl: "http://127.0.0.1:{port}",
      readyUrl: "http://127.0.0.1:{port}/health",
      env: {},
    };
  }

  return undefined;
}

export function normalizeRuntimeConfig(value, serviceName, toolchains) {
  if (!value) {
    return {
      instances: 1,
      maxConcurrentTasks: Number.POSITIVE_INFINITY,
      prepare: {
        inputs: [],
        steps: [],
      },
      toolchain: null,
    };
  }

  return {
    instances: normalizeRuntimeInstances(value.instances ?? 1, `Service "${serviceName}" runtime.instances`),
    maxConcurrentTasks: normalizeRuntimeMaxConcurrentTasks(
      value.maxConcurrentTasks,
      `Service "${serviceName}" runtime.maxConcurrentTasks`
    ),
    prepare: normalizeRuntimePrepareConfig(value.prepare, serviceName),
    toolchain: normalizeRuntimeToolchain(
      value.toolchain,
      `Service "${serviceName}" runtime.toolchain`,
      toolchains || {}
    ),
  };
}

export function normalizeRuntimePrepareConfig(value, serviceName) {
  if (value == null) {
    return {
      inputs: [],
      steps: [],
    };
  }
  if (!value || typeof value !== "object") {
    throw new Error(`Service "${serviceName}" runtime.prepare must be an object`);
  }

  return {
    inputs: normalizeTemplateInputs(value.inputs, `Service "${serviceName}" runtime.prepare`),
    steps: normalizeTemplateLifecycleSteps(value.steps, `Service "${serviceName}" runtime.prepare.steps`),
  };
}

export function normalizeTemplateInputs(value, label) {
  if (value == null) return [];
  if (!Array.isArray(value)) {
    throw new Error(`${label}.inputs must be an array`);
  }

  return value.map((entry, index) => {
    const normalized = normalizeOptionalString(entry);
    if (!normalized) {
      throw new Error(`${label}.inputs[${index}] must be a non-empty string`);
    }
    return normalized;
  });
}

export function normalizeTemplateLifecycleSteps(value, label) {
  if (value == null) return [];
  if (!Array.isArray(value)) {
    throw new Error(`${label} must be an array`);
  }

  return value.map((step, index) => normalizeTemplateLifecycleStep(step, `${label}[${index}]`));
}

export function normalizeTemplateLifecycleStep(step, label) {
  if (!step || typeof step !== "object") {
    throw new Error(`${label} must be an object`);
  }

  const kind = normalizeOptionalString(step.kind);
  if (kind === "command") {
    const cmd = normalizeOptionalString(step.cmd);
    if (!cmd) throw new Error(`${label}.cmd must be a non-empty string`);
    return {
      kind,
      cmd,
      cwd: normalizeOptionalString(step.cwd),
      inputs: normalizeTemplateStepInputs(step.inputs, label),
    };
  }
  if (kind === "sql-file") {
    const filePath = normalizeOptionalString(step.path);
    if (!filePath) throw new Error(`${label}.path must be a non-empty string`);
    return {
      kind,
      path: filePath,
      cwd: normalizeOptionalString(step.cwd),
      inputs: normalizeTemplateStepInputs(step.inputs, label),
    };
  }
  if (kind === "module") {
    const specifier = normalizeOptionalString(step.specifier);
    if (!specifier) throw new Error(`${label}.specifier must be a non-empty string`);
    return {
      kind,
      specifier,
      cwd: normalizeOptionalString(step.cwd),
      inputs: normalizeTemplateStepInputs(step.inputs, label),
    };
  }

  throw new Error(`${label}.kind must be one of: command, sql-file, module`);
}

export function normalizeTemplateStepInputs(value, label) {
  if (value == null) return [];
  if (!Array.isArray(value)) {
    throw new Error(`${label}.inputs must be an array`);
  }

  return value.map((entry, index) => {
    const normalized = normalizeOptionalString(entry);
    if (!normalized) {
      throw new Error(`${label}.inputs[${index}] must be a non-empty string`);
    }
    return normalized;
  });
}

export function normalizeBrowserServiceConfig(value, serviceName) {
  if (!value) return undefined;
  if (typeof value !== "object" || Array.isArray(value)) {
    throw new Error(`Service "${serviceName}" browser config must be an object`);
  }

  const origins = Array.isArray(value.origins)
    ? value.origins.map((origin) => normalizeOptionalString(origin)).filter(Boolean)
    : [];

  for (const origin of origins) {
    try {
      const parsed = new URL(origin);
      if (!parsed.origin) throw new Error("missing origin");
    } catch {
      throw new Error(`Service "${serviceName}" browser.origins contains an invalid URL: ${origin}`);
    }
  }

  if (origins.length === 0) return undefined;
  return { origins };
}

export function normalizeRepoExecution(execution) {
  if (!execution) {
    return normalizeExecutionConfig({
      workers: 1,
      fileTimeoutSeconds: DEFAULT_FILE_TIMEOUT_SECONDS,
    });
  }
  return normalizeExecutionConfig(execution);
}

export function detectNextApp(cwd) {
  return (
    fs.existsSync(path.join(cwd, "next.config.js")) ||
    fs.existsSync(path.join(cwd, "next.config.mjs")) ||
    fs.existsSync(path.join(cwd, "next.config.ts"))
  );
}

export function parseModuleSpecifier(specifier) {
  const [modulePath, exportName] = String(specifier).split("#", 2);
  return {
    modulePath,
    exportName: exportName || "default",
  };
}
