import { z } from "zod";
import { Buffer } from "buffer"; // In Node.js, Buffer is global. Add this if in a non-Node env needing explicit import.
import type {
  DeploymentOptions,
  DeploymentConfig,
  FieldValidationError,
} from "../types/index.js";

// =============================================================================
// Zod Schema Definitions
// =============================================================================

export const LogLevelSchema = z.enum([
  "debug",
  "info",
  "warn",
  "error",
  "silent",
]);

export const DeploymentActionSchema = z.enum([
  "up",
  "preview",
  "destroy",
  "outputs",
  "refresh",
  "rollback",
]);

export const DeploymentStatusSchema = z.enum([
  "initializing",
  "configuring",
  "deploying",
  "completed",
  "failed",
  "rolling-back",
]);

// Enhanced Cloud Configuration Schemas
export const AwsCloudConfigSchema = z.object({
  region: z.string().min(1, "AWS region is required"),
  profile: z.string().optional(),
  accessKeyId: z.string().optional(),
  secretAccessKey: z.string().optional(),
});

export const GcpCloudConfigSchema = z.object({
  project: z.string().min(1, "GCP project is required"),
  region: z.string().min(1, "GCP region is required"),
  zone: z.string().optional(),
  credentials: z.union([z.string(), z.object({}).passthrough()]).optional(), // Path, JSON string, or service account object
});

export const CloudConfigSchema = z.union([
  AwsCloudConfigSchema,
  GcpCloudConfigSchema,
]);

// Kubernetes namespace validation (DNS-1123 compliant)
export const NamespaceSchema = z
  .string()
  .min(1, "Namespace name is required")
  .max(63, "Namespace name must be 63 characters or less")
  .regex(
    /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/,
    "Namespace name must be DNS-1123 compliant: lowercase letters, numbers, and hyphens only, starting and ending with alphanumeric"
  );

export const DeploymentTypeSchema = z.enum(["shared", "dedicated"]);

export const DeploymentConfigSchema = z.object({
  stackName: z
    .string()
    .min(1, "Stack name is required")
    .regex(
      /^[a-zA-Z0-9-_]+$/,
      "Stack name can only contain alphanumeric characters, hyphens, and underscores"
    ),

  secretsJson: z
    .string()
    .min(1, "Secrets JSON is required")
    .refine((val) => {
      try {
        const parsed = JSON.parse(val);
        return typeof parsed === "object" && parsed !== null;
      } catch {
        return false;
      }
    }, "Secrets JSON must be valid JSON object"),
  companyName: z
    .string()
    .min(1, "Company name is required")
    .regex(
      /^[a-zA-Z0-9-_]+$/,
      "Company name can only contain alphanumeric characters, hyphens, and underscores"
    ),

  cloudProvider: z.enum(["aws", "gcp"]).optional(),

  helmChartPath: z.string().optional(),

  namespace: NamespaceSchema.optional(),

  deploymentType: DeploymentTypeSchema.optional(),
});

export const DeploymentOptionsSchema = z.object({
  action: DeploymentActionSchema,
  stackName: z
    .string()
    .min(1, "Stack name is required")
    .regex(
      /^[a-zA-Z0-9-_]+$/,
      "Stack name can only contain alphanumeric characters, hyphens, and underscores"
    ),

  secretsJson: z
    .string()
    .min(1, "Secrets JSON is required")
    .refine((val) => {
      try {
        const parsed = JSON.parse(val);
        return typeof parsed === "object" && parsed !== null;
      } catch {
        return false;
      }
    }, "Secrets JSON must be valid JSON object"),
  companyName: z
    .string()
    .min(1, "Company name is required")
    .regex(
      /^[a-zA-Z0-9-_]+$/,
      "Company name can only contain alphanumeric characters, hyphens, and underscores"
    ),

  workDir: z.string().optional(),
  helmChartPath: z.string().optional(),
  logLevel: LogLevelSchema.optional(),
  validateConfig: z.boolean().optional(),
  enableRollback: z.boolean().optional(),
  timeout: z.number().min(1).max(7200).optional(), // 1 second to 2 hours
  // Enhanced cloud configuration
  cloudProvider: z.enum(["aws", "gcp"]).optional(),
  cloudConfig: CloudConfigSchema.optional(),
  autoSetupConfig: z.boolean().optional(),
  // Namespace-based deployment support
  namespace: NamespaceSchema.optional(),
  deploymentType: DeploymentTypeSchema,
  planTier: z.enum(["basic", "standard", "premium", "enterprise"]).optional(),
  kubecostEnabled: z.boolean().optional(),
  billingAccountId: z.string().optional(),

  // Dynamic Helm values configuration
  defaultDomain: z.string().optional(),
  authDomain: z.string().optional(),
  openPaymentsDomain: z.string().optional(),
  connectorDomain: z.string().optional(),
  enableRafikiAuth: z.boolean().optional(),
  enableRafikiBackend: z.boolean().optional(),
  enableNginx: z.boolean().optional(),
  enableRedis: z.boolean().optional(),

  // Image configuration
  rafikiAuthImage: z
    .object({
      repository: z.string().optional(),
      tag: z.string().optional(),
      pullPolicy: z.string().optional(),
    })
    .optional(),
  rafikiBackendImage: z
    .object({
      repository: z.string().optional(),
      tag: z.string().optional(),
      pullPolicy: z.string().optional(),
    })
    .optional(),
  nginxImage: z
    .object({
      repository: z.string().optional(),
      tag: z.string().optional(),
      pullPolicy: z.string().optional(),
    })
    .optional(),
  redisImage: z
    .object({
      repository: z.string().optional(),
      tag: z.string().optional(),
      pullPolicy: z.string().optional(),
    })
    .optional(),

  // Ingress configuration
  ingressClassName: z.string().optional(),

  // HPA and network policy configuration
  dedicatedDeploymentHpaEnabledByDefault: z.boolean().optional(),
  sharedDeploymentNetworkPolicyEnabled: z.boolean().optional(),
  ingressControllerNamespace: z.string().optional(),
  ingressControllerPodSelectorLabels: z.record(z.string()).optional(),
  allowedExternalEgressRules: z
    .array(
      z.object({
        cidr: z.string(),
        ports: z
          .array(
            z.object({
              port: z.number(),
              protocol: z.enum(["TCP", "UDP"]),
            })
          )
          .optional(),
      })
    )
    .optional(),
});

// =============================================================================
// Enhanced Validation Functions
// =============================================================================

export function validateDeploymentOptionsWithZod(
  options: DeploymentOptions
): FieldValidationError[] {
  const result = DeploymentOptionsSchema.safeParse(options);

  if (result.success) {
    return [];
  }

  return result.error.issues.map((issue) => ({
    field: issue.path.join("."),
    message: issue.message,
    value: issue.path.reduce((obj, key) => obj?.[key], options as any),
  }));
}

export function validateDeploymentConfigWithZod(
  config: DeploymentConfig
): FieldValidationError[] {
  const result = DeploymentConfigSchema.safeParse(config);

  if (result.success) {
    return [];
  }

  return result.error.issues.map((issue) => ({
    field: issue.path.join("."),
    message: issue.message,
    value: issue.path.reduce((obj, key) => obj?.[key], config as any),
  }));
}

// =============================================================================
// Additional Validation Helpers
// =============================================================================

// This function validates the expected structure for Kubernetes secrets processing.
export function validateSecretsStructure(
  secretsJson: string
): FieldValidationError[] {
  const errors: FieldValidationError[] = [];
  try {
    const secrets = JSON.parse(secretsJson);
    if (!secrets.kubernetesSecrets) {
      errors.push({
        field: "kubernetesSecrets",
        message:
          "Top-level 'kubernetesSecrets' key is missing in secrets JSON.",
        value: secrets,
      });
      return errors; // Stop further validation if the main key is missing
    }

    for (const serviceName of Object.keys(secrets.kubernetesSecrets)) {
      const serviceSecretConfig = secrets.kubernetesSecrets[serviceName];
      if (
        typeof serviceSecretConfig !== "object" ||
        serviceSecretConfig === null
      ) {
        errors.push({
          field: `kubernetesSecrets.${serviceName}`,
          message: `Configuration for '${serviceName}' must be an object.`,
          value: serviceSecretConfig,
        });
        continue;
      }
      if (!serviceSecretConfig.stringData) {
        errors.push({
          field: `kubernetesSecrets.${serviceName}.stringData`,
          message: `'stringData' key is missing for service '${serviceName}' in secrets JSON.`,
          value: serviceSecretConfig,
        });
      } else if (
        typeof serviceSecretConfig.stringData !== "object" ||
        serviceSecretConfig.stringData === null
      ) {
        errors.push({
          field: `kubernetesSecrets.${serviceName}.stringData`,
          message: `'stringData' field for service '${serviceName}' must be an object.`,
          value: serviceSecretConfig.stringData,
        });
      }
    }
  } catch (e) {
    // This case is also handled by the main try-catch in validateAndEncodeSecrets,
    // but specific parsing errors for structure can be caught here too.
    errors.push({
      field: "secretsJson",
      message: `Invalid JSON format when validating secrets structure: ${
        e instanceof Error ? e.message : String(e)
      }`,
      value: secretsJson.substring(0, 100) + "...",
    });
  }
  return errors;
}

// =============================================================================
// Secret Processing Utilities (stringData support)
// =============================================================================

/**
 * @deprecated Base64 encoding is no longer needed for stringData secrets.
 * This function is kept for legacy compatibility only.
 * Encodes a single string value to Base64.
 */
export function encodeSecretValue(value: string): string {
  return Buffer.from(value, "utf8").toString("base64");
}

/**
 * @deprecated Base64 decoding is no longer needed for stringData secrets.
 * This function is kept for legacy compatibility only.
 * Decodes a single Base64 encoded string value.
 */
export function decodeSecretValue(encodedValue: string): string {
  return Buffer.from(encodedValue, "base64").toString("utf8");
}

/**
 * @deprecated Base64 detection is no longer needed for stringData secrets.
 * This function is kept for legacy compatibility only.
 * Checks if a string is already Base64 encoded.
 */
export function isBase64Encoded(str: string): boolean {
  if (str === "" || str.trim() === "") {
    return false;
  }
  try {
    // The most reliable way: decode and re-encode. If it's the same, it was valid base64.
    return Buffer.from(str, "base64").toString("base64") === str;
  } catch (e) {
    // If decoding throws an error, it's not valid Base64.
    return false;
  }
}

/**
 * Enhanced version that provides detailed validation and processing for Kubernetes secrets.
 * This function recursively processes the secrets object and validates
 * string values found under any 'stringData' key within the 'kubernetesSecrets' structure.
 * Since we're using stringData, no base64 encoding is needed.
 */
function validateSecretsForKubernetesWithReport(secretsInput: any): {
  processedSecrets: any;
  report: {
    [key: string]: "processed" | "placeholder" | "skipped";
  };
} {
  // Expecting secretsInput to be the full parsed JSON, e.g., { kubernetesSecrets: { ... } }
  const processedSecrets = JSON.parse(JSON.stringify(secretsInput)); // Deep clone
  const report: {
    [key: string]: "processed" | "placeholder" | "skipped";
  } = {};

  function processObject(obj: any, currentPathParts: string[]): void {
    for (const [key, value] of Object.entries(obj)) {
      const newPathParts = [...currentPathParts, key];
      const fullPathKey = newPathParts.join(".");

      if (typeof value === "string") {
        // Check if the immediate parent key is 'stringData'.
        const isInTargetStringDataObject =
          currentPathParts.length > 0 &&
          currentPathParts[currentPathParts.length - 1] === "stringData";

        if (isInTargetStringDataObject) {
          if (
            value.startsWith("PLEASE_REPLACE_WITH_") ||
            value.startsWith("<REPLACE_WITH_") ||
            value.includes("REPLACE_WITH")
          ) {
            report[fullPathKey] = "placeholder";
            console.warn(
              `Found placeholder value for ${fullPathKey}: ${value}`
            );
            continue; // Skip processing for placeholders
          }

          // For stringData, we don't need to encode - just mark as processed
          report[fullPathKey] = "processed";
        } else {
          report[fullPathKey] = "skipped";
        }
      } else if (typeof value === "object" && value !== null) {
        processObject(value, newPathParts);
      } else {
        const isInTargetStringDataObject =
          currentPathParts.length > 0 &&
          currentPathParts[currentPathParts.length - 1] === "stringData";
        if (!isInTargetStringDataObject) {
          report[fullPathKey] = "skipped";
        } else {
          if (
            value !== null &&
            (typeof value === "number" || typeof value === "boolean")
          ) {
            // Convert numbers and booleans to strings for Kubernetes secret stringData values
            obj[key] = String(value);
            report[fullPathKey] = "processed";
          } else {
            report[fullPathKey] = "skipped"; // Other types (null, undefined under stringData)
          }
        }
      }
    }
  }

  // Start processing from the root of the cloned secrets object
  if (processedSecrets.kubernetesSecrets) {
    processObject(processedSecrets.kubernetesSecrets, ["kubernetesSecrets"]);
  } else {
    // If kubernetesSecrets key is missing, the report will be empty,
    // and no processing will happen. Structure validation should catch this.
    console.warn(
      "validateSecretsForKubernetesWithReport: 'kubernetesSecrets' key not found in input. No secrets will be processed."
    );
  }
  return { processedSecrets, report };
}

export function validateAndProcessSecrets(secretsJson: string): {
  isValid: boolean;
  processedSecretsJson: string;
  errors: FieldValidationError[];
  processingReport?: {
    [key: string]: "processed" | "placeholder" | "skipped";
  };
} {
  let errors: FieldValidationError[] = [];
  let processingReport: {
    [key: string]: "processed" | "placeholder" | "skipped";
  } = {};

  try {
    const secrets = JSON.parse(secretsJson);

    const structureErrors = validateSecretsStructure(secretsJson); // Pass the raw JSON string
    errors.push(...structureErrors);

    if (errors.length > 0) {
      return {
        isValid: false,
        processedSecretsJson: secretsJson,
        errors,
        // processingReport will be empty or undefined here
      };
    }

    const { processedSecrets, report } =
      validateSecretsForKubernetesWithReport(secrets); // Pass the parsed 'secrets' object

    processingReport = report;
    console.log("Secret processing report:", report);

    for (const [key, status] of Object.entries(report)) {
      if (status === "placeholder") {
        // Attempt to get the original placeholder value for the error message
        let placeholderValue = "PLACEHOLDER_VALUE_NOT_RETRIEVED";
        const pathParts = key.split(".");
        try {
          placeholderValue = pathParts.reduce(
            (acc, part) => acc && acc[part],
            secrets
          );
        } catch (e) {
          /* ignore if path is invalid, shouldn't happen */
        }

        errors.push({
          field: key,
          message: `Placeholder value found for '${key}'. Please replace '${placeholderValue}'.`,
          value: placeholderValue,
        });
      }
    }

    if (errors.length > 0) {
      return {
        isValid: false, // If placeholders are considered errors making it invalid
        processedSecretsJson: JSON.stringify(processedSecrets), // Still return the (partially) processed secrets
        errors,
        processingReport,
      };
    }

    return {
      isValid: true,
      processedSecretsJson: JSON.stringify(processedSecrets),
      errors: [], // errors is empty if we reach here
      processingReport: report,
    };
  } catch (e) {
    // This catch is primarily for JSON.parse(secretsJson) failure
    errors.push({
      field: "secretsJson",
      message: `Invalid JSON format for secrets: ${
        e instanceof Error ? e.message : String(e)
      }`,
      value: secretsJson.substring(0, 100) + "...",
    });

    return {
      isValid: false,
      processedSecretsJson: secretsJson,
      errors,
      processingReport,
    };
  }
}

// Legacy function name for backward compatibility - now just calls the new function
export function validateAndEncodeSecrets(secretsJson: string): {
  isValid: boolean;
  encodedSecretsJson: string;
  errors: FieldValidationError[];
  encodingReport?: {
    [key: string]: "encoded" | "already_encoded" | "placeholder" | "skipped";
  };
} {
  const result = validateAndProcessSecrets(secretsJson);

  // Map the new report statuses to old ones for backward compatibility
  const encodingReport: {
    [key: string]: "encoded" | "already_encoded" | "placeholder" | "skipped";
  } = {};

  if (result.processingReport) {
    for (const [key, status] of Object.entries(result.processingReport)) {
      switch (status) {
        case "processed":
          encodingReport[key] = "encoded"; // For backward compatibility
          break;
        case "placeholder":
          encodingReport[key] = "placeholder";
          break;
        case "skipped":
          encodingReport[key] = "skipped";
          break;
      }
    }
  }

  return {
    isValid: result.isValid,
    encodedSecretsJson: result.processedSecretsJson,
    errors: result.errors,
    encodingReport,
  };
}

// =============================================================================
// Type Guards
// =============================================================================

export function isValidDeploymentAction(
  action: string
): action is z.infer<typeof DeploymentActionSchema> {
  return DeploymentActionSchema.safeParse(action).success;
}

export function isValidLogLevel(
  level: string
): level is z.infer<typeof LogLevelSchema> {
  return LogLevelSchema.safeParse(level).success;
}

export function isValidDeploymentStatus(
  status: string
): status is z.infer<typeof DeploymentStatusSchema> {
  return DeploymentStatusSchema.safeParse(status).success;
}

export function isValidNamespace(
  namespace: string
): namespace is z.infer<typeof NamespaceSchema> {
  return NamespaceSchema.safeParse(namespace).success;
}

export function isValidDeploymentType(
  deploymentType: string
): deploymentType is z.infer<typeof DeploymentTypeSchema> {
  return DeploymentTypeSchema.safeParse(deploymentType).success;
}

// =============================================================================
// Namespace-specific Validation Functions
// =============================================================================

export function validateNamespaceConfiguration(
  namespace: string,
  deploymentType: "shared" | "dedicated"
): FieldValidationError[] {
  const errors: FieldValidationError[] = [];

  // Validate namespace name
  if (!isValidNamespace(namespace)) {
    errors.push({
      field: "namespace",
      message:
        "Invalid namespace name. Must be DNS-1123 compliant: lowercase letters, numbers, and hyphens only, starting and ending with alphanumeric characters, max 63 chars",
      value: namespace,
    });
  }

  // For shared deployments, namespace is required
  if (deploymentType === "shared" && !namespace) {
    errors.push({
      field: "namespace",
      message: "Namespace is required for shared cluster deployments",
      value: namespace,
    });
  }

  // Reserved namespace names
  const reservedNamespaces = [
    "default",
    "kube-system",
    "kube-public",
    "kube-node-lease",
    "kubernetes-dashboard",
  ];

  if (reservedNamespaces.includes(namespace)) {
    errors.push({
      field: "namespace",
      message: `Namespace '${namespace}' is reserved and cannot be used for deployments`,
      value: namespace,
    });
  }

  return errors;
}
