import type { StandardSchemaV1 } from "@standard-schema/spec";
import {
  ConfigValidationError,
  parseStandardSchemaAsync,
  readEnv,
} from "../../config/index.js";
import { SchemaValidationError } from "../../errors/index.js";
import type { AnyPorts } from "../../ports/index.js";
import type { ServiceProvider } from "../../providers/index.js";

type ProviderConfigDef<Schema extends StandardSchemaV1<unknown, unknown>> = {
  schema: Schema;
  envPrefix?: string;
  overrides?: Record<string, unknown>;
};

async function parseStandardSchema<
  Schema extends StandardSchemaV1<unknown, unknown>,
>(
  schema: Schema,
  input: unknown,
): Promise<StandardSchemaV1.InferOutput<Schema>> {
  try {
    return await parseStandardSchemaAsync(schema, input);
  } catch (error) {
    if (error instanceof ConfigValidationError) {
      throw new SchemaValidationError(error.issues);
    }

    throw error;
  }
}

/**
 * Load and validate config for a provider.
 *
 * Explicit overrides take precedence over env-derived config. When a provider
 * declares `envPrefix`, matching env keys are stripped before validation.
 */
export async function loadProviderConfig<
  Ports extends AnyPorts,
  Schema extends StandardSchemaV1<unknown, unknown>,
>(
  provider: ServiceProvider<Ports, Schema>,
  env: Record<string, string | undefined>,
  overrides: Record<string, unknown>,
): Promise<StandardSchemaV1.InferOutput<Schema> | undefined> {
  const configDef = provider.config as ProviderConfigDef<Schema> | undefined;
  if (!configDef) return undefined;

  const { schema, envPrefix, overrides: fieldOverrides } = configDef;
  const hasOverride = Object.hasOwn(overrides, provider.name);
  const base = hasOverride
    ? overrides[provider.name]
    : envPrefix
      ? readEnv({ env, prefix: envPrefix })
      : {};

  // Field-level overrides from the provider's own config definition (factory
  // options) win over env-derived and server-supplied values. Defined values
  // only, so absent options fall back to the base input and schema defaults.
  let input = base;
  if (fieldOverrides) {
    const defined = Object.fromEntries(
      Object.entries(fieldOverrides).filter(([, value]) => value !== undefined),
    );
    if (Object.keys(defined).length > 0) {
      input = {
        ...(typeof base === "object" && base !== null ? base : {}),
        ...defined,
      };
    }
  }

  try {
    return await parseStandardSchema(schema, input);
  } catch (error) {
    const suffix = envPrefix ? ` (envPrefix=${envPrefix})` : "";
    const found = envPrefix
      ? Object.keys(env).filter(
          (k) => k.startsWith(envPrefix) && env[k] !== undefined,
        )
      : [];
    const hint = envPrefix
      ? found.length
        ? `\n  Env vars found with prefix "${envPrefix}": ${found.join(", ")}`
        : `\n  No env vars found with prefix "${envPrefix}"`
      : "";
    throw new Error(
      `Failed to load config for provider "${provider.name}"${suffix}: ${(error as Error).message}${hint}`,
    );
  }
}

/**
 * Parse provider config with Standard Schema and normalize validation errors.
 */
export { parseStandardSchema };
