import { config as dotenv } from 'dotenv';
import zod from 'zod';

// Prefer isolated test env file when provided, otherwise fall back to repo-level .env
dotenv({
  path:
    process.env.TEST_ENV_FILE && process.env.TEST_ENV_FILE.trim() !== ''
      ? process.env.TEST_ENV_FILE
      : ['.env', '../../.env'],
  debug: process.env.RELEASE ? false : true,
});

const isNumberString = (input: unknown) => zod.string().regex(/^\d+$/).safeParse(input).success;

const numberFromNumberOrNumberString = (input: unknown): number | undefined => {
  if (typeof input === 'number') return input;
  if (isNumberString(input)) return Number(input);
  return undefined;
};

const NumberFromString = zod.preprocess(numberFromNumberOrNumberString, zod.number().min(1));

// treat an empty string (`''`) as undefined
const emptyString = <T extends zod.ZodType>(input: T) => {
  return zod.preprocess((value: unknown) => {
    if (value === '') return undefined;
    return value;
  }, input);
};

const PostgresModel = zod.object({
  POSTGRES_SSL: emptyString(zod.union([zod.literal('1'), zod.literal('0')]).optional()),
  POSTGRES_HOST: zod.string(),
  POSTGRES_PORT: NumberFromString,
  POSTGRES_DB: zod.string(),
  POSTGRES_USER: zod.string(),
  POSTGRES_PASSWORD: zod.string(),
  POSTGRES_MAX_CLIENTS: emptyString(NumberFromString).optional().default(20),
});

const CloudinaryModel = zod.union([
  zod
    .object({
      CLOUDINARY_NAME: zod.string().optional(),
      CLOUDINARY_API_KEY: zod.string().optional(),
      CLOUDINARY_API_SECRET: zod.string().optional(),
    })
    .superRefine((data, ctx) => {
      if (
        !!data.CLOUDINARY_NAME !== !!data.CLOUDINARY_API_KEY ||
        !!data.CLOUDINARY_NAME !== !!data.CLOUDINARY_API_SECRET
      ) {
        ctx.addIssue({
          code: 'custom',
          message:
            'CLOUDINARY_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET must be provided together.',
        });
      }
    }),
  zod.void(),
]);

const GreenInvoiceModel = zod.union([
  zod
    .object({
      GREEN_INVOICE_ID: zod.string().optional(),
      GREEN_INVOICE_SECRET: zod.string().optional(),
    })
    .superRefine((data, ctx) => {
      if (!!data.GREEN_INVOICE_ID !== !!data.GREEN_INVOICE_SECRET) {
        ctx.addIssue({
          code: 'custom',
          message: 'GREEN_INVOICE_ID and GREEN_INVOICE_SECRET must be provided together.',
        });
      }
    }),
  zod.void(),
]);

const HiveModel = zod.union([
  zod.object({
    HIVE_TOKEN: zod.string().optional(),
  }),
  zod.void(),
]);

const GoogleDriveModel = zod.union([
  zod.object({
    GOOGLE_DRIVE_API_KEY: zod.string().optional(),
  }),
  zod.void(),
]);

const DeelModel = zod.union([
  zod.object({
    DEEL_TOKEN: zod.string().optional(),
  }),
  zod.void(),
]);

const CredentialsModel = zod.object({
  CREDENTIALS_ENCRYPTION_KEY: zod
    .string()
    .trim()
    .regex(/^[a-f0-9]{64}$/i),
});

const GeneralModel = zod.object({
  FRONTEND_URL: zod.url().optional(),
});

const OtelModel = zod
  .object({
    OTEL_ENABLED: emptyString(
      zod
        .union([zod.literal('1'), zod.literal('0')])
        .optional()
        .default('0'),
    ),
    OTEL_SERVICE_NAME: emptyString(zod.string().optional().default('accounter-server')),
    OTEL_SERVICE_NAMESPACE: emptyString(zod.string().optional().default('accounter')),
    OTEL_DEPLOYMENT_ENV: emptyString(
      zod
        .string()
        .optional()
        .default(process.env.NODE_ENV ?? 'development'),
    ),
    OTEL_EXPORTER_OTLP_ENDPOINT: emptyString(zod.string().optional()),
    OTEL_EXPORTER_OTLP_HEADERS: emptyString(zod.string().optional()),
    OTEL_TRACES_SAMPLER: emptyString(
      zod
        .enum([
          'parentbased_traceidratio',
          'always_on',
          'always_off',
          'traceidratio',
          'parentbased_always_on',
          'parentbased_always_off',
        ])
        .optional()
        .default('always_on'),
    ),
    OTEL_TRACES_SAMPLER_ARG: emptyString(zod.string().optional()),
    OTEL_STARTUP_STRICT: emptyString(
      zod.union([zod.literal('true'), zod.literal('false')]).optional(),
    ),
  })
  .superRefine((data, ctx) => {
    if (data.OTEL_ENABLED === '1' && !data.OTEL_EXPORTER_OTLP_ENDPOINT) {
      ctx.addIssue({
        code: 'custom',
        path: ['OTEL_EXPORTER_OTLP_ENDPOINT'],
        message: 'OTEL_EXPORTER_OTLP_ENDPOINT is required when OTEL_ENABLED is "1".',
      });
    }

    const usesRatioSampler =
      data.OTEL_TRACES_SAMPLER === 'parentbased_traceidratio' ||
      data.OTEL_TRACES_SAMPLER === 'traceidratio';

    if (usesRatioSampler) {
      if (data.OTEL_TRACES_SAMPLER_ARG === undefined) {
        ctx.addIssue({
          code: 'custom',
          path: ['OTEL_TRACES_SAMPLER_ARG'],
          message:
            'OTEL_TRACES_SAMPLER_ARG is required when OTEL_TRACES_SAMPLER is a ratio sampler ("traceidratio" or "parentbased_traceidratio").',
        });
      } else {
        const num = Number(data.OTEL_TRACES_SAMPLER_ARG);
        if (!Number.isFinite(num) || num < 0 || num > 1) {
          ctx.addIssue({
            code: 'custom',
            path: ['OTEL_TRACES_SAMPLER_ARG'],
            message: 'OTEL_TRACES_SAMPLER_ARG must be a numeric string between 0 and 1.',
          });
        }
      }
    }
  });

const Auth0Model = zod.union([
  zod.object({
    AUTH0_DOMAIN: zod.string().min(1),
    AUTH0_AUDIENCE: zod.string().min(1),
    AUTH0_CLIENT_ID: zod.string().min(1),
    AUTH0_CLIENT_SECRET: zod.string().min(1),
    AUTH0_MANAGEMENT_AUDIENCE: zod.string().min(1),
  }),
  // If no Auth0 variables are provided, validation passes (optional configuration)
  // We use a looser object check here because process.env is always an object
  zod
    .object({
      AUTH0_DOMAIN: zod.literal('').optional(),
      AUTH0_AUDIENCE: zod.literal('').optional(),
      AUTH0_CLIENT_ID: zod.literal('').optional(),
      AUTH0_CLIENT_SECRET: zod.literal('').optional(),
      AUTH0_MANAGEMENT_AUDIENCE: zod.literal('').optional(),
    })
    .transform(() => undefined),
]);

const configs = {
  postgres: PostgresModel.safeParse(process.env),
  cloudinary: CloudinaryModel.safeParse(process.env),
  greenInvoice: GreenInvoiceModel.safeParse(process.env),
  hive: HiveModel.safeParse(process.env),
  googleDrive: GoogleDriveModel.safeParse(process.env),
  auth0: Auth0Model.safeParse(process.env),
  deel: DeelModel.safeParse(process.env),
  credentials: CredentialsModel.safeParse(process.env),
  general: GeneralModel.safeParse(process.env),
  otel: OtelModel.safeParse(process.env),
};

const environmentErrors: Array<string> = [];

for (const config of Object.values(configs)) {
  if (config.success === false) {
    environmentErrors.push(JSON.stringify(config.error.format(), null, 4));
  }
}

if (environmentErrors.length) {
  const fullError = environmentErrors.join(`\n`);
  console.error('❌ Invalid environment variables:', fullError);
  process.exit(1);
}

function extractConfig<Output>(config: zod.ZodSafeParseResult<Output>): Output {
  if (!config.success) {
    throw new Error('Something went wrong.');
  }
  return config.data;
}

const postgres = extractConfig(configs.postgres);
const cloudinary = extractConfig(configs.cloudinary);
const greenInvoice = extractConfig(configs.greenInvoice);
const hive = extractConfig(configs.hive);
const googleDrive = extractConfig(configs.googleDrive);
const auth0 = extractConfig(configs.auth0);
const deel = extractConfig(configs.deel);
const credentials = extractConfig(configs.credentials);
const general = extractConfig(configs.general);
const otel = extractConfig(configs.otel);

export const env = {
  postgres: {
    host: postgres.POSTGRES_HOST,
    port: postgres.POSTGRES_PORT,
    db: postgres.POSTGRES_DB,
    user: postgres.POSTGRES_USER,
    password: postgres.POSTGRES_PASSWORD,
    ssl: postgres.POSTGRES_SSL === '1',
    max: postgres.POSTGRES_MAX_CLIENTS,
  },
  cloudinary: cloudinary?.CLOUDINARY_API_KEY
    ? {
        name: cloudinary.CLOUDINARY_NAME!,
        apiKey: cloudinary.CLOUDINARY_API_KEY!,
        apiSecret: cloudinary.CLOUDINARY_API_SECRET!,
      }
    : undefined,
  greenInvoice: greenInvoice?.GREEN_INVOICE_ID
    ? {
        id: greenInvoice.GREEN_INVOICE_ID!,
        secret: greenInvoice.GREEN_INVOICE_SECRET!,
      }
    : undefined,
  hive: hive?.HIVE_TOKEN
    ? {
        hiveToken: hive.HIVE_TOKEN!,
      }
    : undefined,
  googleDrive: googleDrive?.GOOGLE_DRIVE_API_KEY
    ? {
        driveApiKey: googleDrive.GOOGLE_DRIVE_API_KEY!,
      }
    : undefined,
  deel: deel?.DEEL_TOKEN
    ? {
        apiToken: deel.DEEL_TOKEN!,
      }
    : undefined,
  credentialsEncryptionKey: credentials.CREDENTIALS_ENCRYPTION_KEY,
  auth0: auth0
    ? {
        domain: auth0.AUTH0_DOMAIN,
        audience: auth0.AUTH0_AUDIENCE,
        clientId: auth0.AUTH0_CLIENT_ID,
        clientSecret: auth0.AUTH0_CLIENT_SECRET,
        managementAudience: auth0.AUTH0_MANAGEMENT_AUDIENCE,
      }
    : undefined,
  general: {
    frontendUrl: general?.FRONTEND_URL,
  },
  otel: {
    enabled: otel.OTEL_ENABLED === '1',
    serviceName: otel.OTEL_SERVICE_NAME,
    serviceNamespace: otel.OTEL_SERVICE_NAMESPACE,
    deploymentEnv: otel.OTEL_DEPLOYMENT_ENV,
    exporterEndpoint: otel.OTEL_EXPORTER_OTLP_ENDPOINT,
    exporterHeaders: otel.OTEL_EXPORTER_OTLP_HEADERS,
    tracesSampler: otel.OTEL_TRACES_SAMPLER,
    tracesSamplerArg:
      (otel.OTEL_TRACES_SAMPLER === 'parentbased_traceidratio' ||
        otel.OTEL_TRACES_SAMPLER === 'traceidratio') &&
      otel.OTEL_TRACES_SAMPLER_ARG !== undefined
        ? Number(otel.OTEL_TRACES_SAMPLER_ARG)
        : undefined,
    startupStrict: otel.OTEL_STARTUP_STRICT === 'true',
  },
} as const;
