import * as plugins from './plugins.js';
import * as paths from './paths.js';

/**
 * Authentication configuration
 */
export interface IAuthConfig {
  enabled: boolean;
  credentials: Array<{
    accessKeyId: string;
    secretAccessKey: string;
  }>;
}

/**
 * CORS configuration
 */
export interface ICorsConfig {
  enabled: boolean;
  allowedOrigins?: string[];
  allowedMethods?: string[];
  allowedHeaders?: string[];
  exposedHeaders?: string[];
  maxAge?: number;
  allowCredentials?: boolean;
}

/**
 * Logging configuration
 */
export interface ILoggingConfig {
  level?: 'error' | 'warn' | 'info' | 'debug';
  format?: 'text' | 'json';
  enabled?: boolean;
}

/**
 * Request limits configuration
 */
export interface ILimitsConfig {
  maxObjectSize?: number;
  maxMetadataSize?: number;
  requestTimeout?: number;
}

/**
 * Multipart upload configuration
 */
export interface IMultipartConfig {
  expirationDays?: number;
  cleanupIntervalMinutes?: number;
}

/**
 * Server configuration
 */
export interface IServerConfig {
  port?: number;
  address?: string;
  silent?: boolean;
  region?: string;
}

/**
 * Storage configuration
 */
export interface IStorageConfig {
  directory?: string;
  cleanSlate?: boolean;
}

/**
 * Erasure coding configuration
 */
export interface IErasureConfig {
  dataShards?: number;
  parityShards?: number;
  chunkSizeBytes?: number;
}

/**
 * Drive configuration for multi-drive support
 */
export interface IDriveConfig {
  paths: string[];
}

/**
 * Cluster configuration for distributed mode
 */
export interface IClusterConfig {
  enabled: boolean;
  nodeId?: string;
  quicPort?: number;
  seedNodes?: string[];
  erasure?: IErasureConfig;
  drives?: IDriveConfig;
  heartbeatIntervalMs?: number;
  heartbeatTimeoutMs?: number;
}

/**
 * Complete smartstorage configuration
 */
export interface ISmartStorageConfig {
  server?: IServerConfig;
  storage?: IStorageConfig;
  auth?: IAuthConfig;
  cors?: ICorsConfig;
  logging?: ILoggingConfig;
  limits?: ILimitsConfig;
  multipart?: IMultipartConfig;
  cluster?: IClusterConfig;
}

/**
 * Default configuration values
 */
const DEFAULT_CONFIG: ISmartStorageConfig = {
  server: {
    port: 3000,
    address: '0.0.0.0',
    silent: false,
    region: 'us-east-1',
  },
  storage: {
    directory: paths.bucketsDir,
    cleanSlate: false,
  },
  auth: {
    enabled: false,
    credentials: [
      {
        accessKeyId: 'STORAGE',
        secretAccessKey: 'STORAGE',
      },
    ],
  },
  cors: {
    enabled: false,
    allowedOrigins: ['*'],
    allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'],
    allowedHeaders: ['*'],
    exposedHeaders: ['ETag', 'x-amz-request-id', 'x-amz-version-id'],
    maxAge: 86400,
    allowCredentials: false,
  },
  logging: {
    level: 'info',
    format: 'text',
    enabled: true,
  },
  limits: {
    maxObjectSize: 5 * 1024 * 1024 * 1024, // 5GB
    maxMetadataSize: 2048,
    requestTimeout: 300000, // 5 minutes
  },
  multipart: {
    expirationDays: 7,
    cleanupIntervalMinutes: 60,
  },
};

/**
 * Merge user config with defaults (deep merge)
 */
function mergeConfig(userConfig: ISmartStorageConfig): Required<ISmartStorageConfig> {
  return {
    server: {
      ...DEFAULT_CONFIG.server!,
      ...(userConfig.server || {}),
    },
    storage: {
      ...DEFAULT_CONFIG.storage!,
      ...(userConfig.storage || {}),
    },
    auth: {
      ...DEFAULT_CONFIG.auth!,
      ...(userConfig.auth || {}),
    },
    cors: {
      ...DEFAULT_CONFIG.cors!,
      ...(userConfig.cors || {}),
    },
    logging: {
      ...DEFAULT_CONFIG.logging!,
      ...(userConfig.logging || {}),
    },
    limits: {
      ...DEFAULT_CONFIG.limits!,
      ...(userConfig.limits || {}),
    },
    multipart: {
      ...DEFAULT_CONFIG.multipart!,
      ...(userConfig.multipart || {}),
    },
    ...(userConfig.cluster ? { cluster: userConfig.cluster } : {}),
  } as Required<ISmartStorageConfig>;
}

/**
 * IPC command type map for RustBridge
 */
type TRustStorageCommands = {
  start: { params: { config: Required<ISmartStorageConfig> }; result: {} };
  stop: { params: {}; result: {} };
  createBucket: { params: { name: string }; result: {} };
};

/**
 * Main SmartStorage class - production-ready S3-compatible storage server
 */
export class SmartStorage {
  // STATIC
  public static async createAndStart(configArg: ISmartStorageConfig = {}) {
    const smartStorageInstance = new SmartStorage(configArg);
    await smartStorageInstance.start();
    return smartStorageInstance;
  }

  // INSTANCE
  public config: Required<ISmartStorageConfig>;
  private bridge: InstanceType<typeof plugins.RustBridge<TRustStorageCommands>>;

  constructor(configArg: ISmartStorageConfig = {}) {
    this.config = mergeConfig(configArg);
    this.bridge = new plugins.RustBridge<TRustStorageCommands>({
      binaryName: 'ruststorage',
      localPaths: [
        plugins.path.join(paths.packageDir, 'dist_rust', 'ruststorage'),
      ],
      readyTimeoutMs: 30000,
      requestTimeoutMs: 300000,
    });
  }

  public async start() {
    const spawned = await this.bridge.spawn();
    if (!spawned) {
      throw new Error('Failed to spawn ruststorage binary. Make sure it is compiled (pnpm build).');
    }
    await this.bridge.sendCommand('start', { config: this.config });

    if (!this.config.server.silent) {
      console.log('storage server is running');
    }
  }

  public async getStorageDescriptor(
    optionsArg?: Partial<plugins.tsclass.storage.IS3Descriptor>,
  ): Promise<plugins.tsclass.storage.IS3Descriptor> {
    const cred = this.config.auth.credentials[0] || {
      accessKeyId: 'STORAGE',
      secretAccessKey: 'STORAGE',
    };

    const descriptor: plugins.tsclass.storage.IS3Descriptor = {
      endpoint: this.config.server.address === '0.0.0.0' ? 'localhost' : this.config.server.address!,
      port: this.config.server.port!,
      useSsl: false,
      accessKey: cred.accessKeyId,
      accessSecret: cred.secretAccessKey,
      bucketName: '',
    };

    return {
      ...descriptor,
      ...(optionsArg ? optionsArg : {}),
    };
  }

  public async createBucket(bucketNameArg: string) {
    await this.bridge.sendCommand('createBucket', { name: bucketNameArg });
    return { name: bucketNameArg };
  }

  public async stop() {
    await this.bridge.sendCommand('stop', {});
    this.bridge.kill();
  }
}
