/**
 * Synor Storage SDK Client
 *
 * Decentralized storage, pinning, and content retrieval.
 *
 * @example
 * ```typescript
 * import { SynorStorage } from '@synor/storage';
 *
 * const storage = new SynorStorage({ apiKey: 'your-api-key' });
 *
 * // Upload a file
 * const result = await storage.upload(Buffer.from('Hello, World!'));
 * console.log('CID:', result.cid);
 *
 * // Download content
 * const data = await storage.download(result.cid);
 * console.log('Content:', data.toString());
 * ```
 */

import type {
  StorageConfig,
  UploadOptions,
  UploadResponse,
  DownloadOptions,
  Pin,
  PinRequest,
  ListPinsOptions,
  ListPinsResponse,
  GatewayUrl,
  CarFile,
  FileEntry,
  DirectoryEntry,
  ImportCarResponse,
  StorageStats,
  StorageError,
} from './types';

const DEFAULT_ENDPOINT = 'https://storage.synor.cc/api/v1';
const DEFAULT_GATEWAY = 'https://gateway.synor.cc';
const DEFAULT_CHUNK_SIZE = 262144; // 256KB
const DEFAULT_TIMEOUT = 30000;

export class SynorStorage {
  private config: Required<Omit<StorageConfig, 'pinningService'>> & Pick<StorageConfig, 'pinningService'>;

  constructor(config: StorageConfig) {
    this.config = {
      apiKey: config.apiKey,
      endpoint: config.endpoint || DEFAULT_ENDPOINT,
      gateway: config.gateway || DEFAULT_GATEWAY,
      pinningService: config.pinningService,
      chunkSize: config.chunkSize || DEFAULT_CHUNK_SIZE,
      timeout: config.timeout || DEFAULT_TIMEOUT,
      debug: config.debug || false,
    };
  }

  /**
   * Upload content to storage.
   */
  async upload(
    data: Buffer | Uint8Array | string,
    options: UploadOptions = {}
  ): Promise<UploadResponse> {
    const formData = new FormData();
    const blob = new Blob([data]);
    formData.append('file', blob);

    const params = new URLSearchParams();
    if (options.pin !== undefined) params.set('pin', String(options.pin));
    if (options.wrapWithDirectory) params.set('wrapWithDirectory', 'true');
    if (options.cidVersion !== undefined) params.set('cidVersion', String(options.cidVersion));
    if (options.hashAlgorithm) params.set('hashAlgorithm', options.hashAlgorithm);

    const url = `${this.config.endpoint}/upload${params.toString() ? `?${params}` : ''}`;

    const response = await this.fetchWithAuth(url, {
      method: 'POST',
      body: formData,
    });

    return response as UploadResponse;
  }

  /**
   * Upload a file from a path (Node.js only).
   */
  async uploadFile(
    filePath: string,
    options: UploadOptions = {}
  ): Promise<UploadResponse> {
    // This would need fs module in Node.js
    // For browser, use upload() with File object
    const response = await this.request('POST', '/upload/file', {
      filePath,
      ...options,
    });
    return response as UploadResponse;
  }

  /**
   * Download content by CID.
   */
  async download(cid: string, options: DownloadOptions = {}): Promise<Buffer> {
    const params = new URLSearchParams();
    if (options.offset !== undefined) params.set('offset', String(options.offset));
    if (options.length !== undefined) params.set('length', String(options.length));

    const url = `${this.config.endpoint}/content/${cid}${params.toString() ? `?${params}` : ''}`;

    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${this.config.apiKey}`,
      },
    });

    if (!response.ok) {
      await this.handleError(response);
    }

    const arrayBuffer = await response.arrayBuffer();
    return Buffer.from(arrayBuffer);
  }

  /**
   * Download content as a stream.
   */
  async downloadStream(cid: string, options: DownloadOptions = {}): Promise<ReadableStream<Uint8Array>> {
    const params = new URLSearchParams();
    if (options.offset !== undefined) params.set('offset', String(options.offset));
    if (options.length !== undefined) params.set('length', String(options.length));

    const url = `${this.config.endpoint}/content/${cid}/stream${params.toString() ? `?${params}` : ''}`;

    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${this.config.apiKey}`,
      },
    });

    if (!response.ok) {
      await this.handleError(response);
    }

    if (!response.body) {
      throw new Error('No response body');
    }

    return response.body;
  }

  /**
   * Pin content by CID.
   */
  async pin(request: PinRequest): Promise<Pin> {
    const response = await this.request('POST', '/pins', request);
    return response as Pin;
  }

  /**
   * Unpin content by CID.
   */
  async unpin(cid: string): Promise<void> {
    await this.request('DELETE', `/pins/${cid}`);
  }

  /**
   * Get pin status by CID.
   */
  async getPinStatus(cid: string): Promise<Pin> {
    const response = await this.request('GET', `/pins/${cid}`);
    return response as Pin;
  }

  /**
   * List pins.
   */
  async listPins(options: ListPinsOptions = {}): Promise<ListPinsResponse> {
    const params = new URLSearchParams();
    if (options.status) params.set('status', options.status.join(','));
    if (options.match) params.set('match', options.match);
    if (options.name) params.set('name', options.name);
    if (options.limit !== undefined) params.set('limit', String(options.limit));
    if (options.offset !== undefined) params.set('offset', String(options.offset));

    const response = await this.request('GET', `/pins?${params}`);
    return response as ListPinsResponse;
  }

  /**
   * Get gateway URL for content.
   */
  getGatewayUrl(cid: string, path?: string): GatewayUrl {
    const fullPath = path ? `/${cid}/${path}` : `/${cid}`;
    return {
      url: `${this.config.gateway}/ipfs${fullPath}`,
      cid,
      path,
    };
  }

  /**
   * Create a CAR file from files.
   */
  async createCar(files: FileEntry[]): Promise<CarFile> {
    const response = await this.request('POST', '/car/create', { files });
    return response as CarFile;
  }

  /**
   * Import a CAR file.
   */
  async importCar(carData: Buffer | string, pin = true): Promise<ImportCarResponse> {
    const base64 = typeof carData === 'string' ? carData : carData.toString('base64');
    const response = await this.request('POST', '/car/import', {
      car: base64,
      pin,
    });
    return response as ImportCarResponse;
  }

  /**
   * Export content as a CAR file.
   */
  async exportCar(cid: string): Promise<Buffer> {
    const url = `${this.config.endpoint}/car/export/${cid}`;

    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${this.config.apiKey}`,
      },
    });

    if (!response.ok) {
      await this.handleError(response);
    }

    const arrayBuffer = await response.arrayBuffer();
    return Buffer.from(arrayBuffer);
  }

  /**
   * Create a directory from files.
   */
  async createDirectory(files: FileEntry[]): Promise<UploadResponse> {
    const response = await this.request('POST', '/directory', { files });
    return response as UploadResponse;
  }

  /**
   * List directory contents.
   */
  async listDirectory(cid: string, path?: string): Promise<DirectoryEntry[]> {
    const params = path ? `?path=${encodeURIComponent(path)}` : '';
    const response = await this.request('GET', `/directory/${cid}${params}`);
    return (response as { entries: DirectoryEntry[] }).entries;
  }

  /**
   * Get storage statistics.
   */
  async getStats(): Promise<StorageStats> {
    const response = await this.request('GET', '/stats');
    return response as StorageStats;
  }

  /**
   * Check if content exists.
   */
  async exists(cid: string): Promise<boolean> {
    try {
      await this.request('HEAD', `/content/${cid}`);
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Get content metadata.
   */
  async getMetadata(cid: string): Promise<{ size: number; type: string }> {
    const response = await this.request('GET', `/content/${cid}/metadata`);
    return response as { size: number; type: string };
  }

  private async request(
    method: string,
    path: string,
    body?: unknown
  ): Promise<unknown> {
    const url = `${this.config.endpoint}${path}`;

    if (this.config.debug) {
      console.log(`[SynorStorage] ${method} ${path}`);
    }

    const response = await this.fetchWithAuth(url, {
      method,
      body: body ? JSON.stringify(body) : undefined,
    });

    return response;
  }

  private async fetchWithAuth(url: string, options: RequestInit): Promise<unknown> {
    const headers: Record<string, string> = {
      Authorization: `Bearer ${this.config.apiKey}`,
    };

    if (options.body && typeof options.body === 'string') {
      headers['Content-Type'] = 'application/json';
    }

    const response = await fetch(url, {
      ...options,
      headers: {
        ...headers,
        ...(options.headers as Record<string, string>),
      },
      signal: AbortSignal.timeout(this.config.timeout),
    });

    if (!response.ok) {
      await this.handleError(response);
    }

    if (response.status === 204 || options.method === 'HEAD') {
      return {};
    }

    return response.json();
  }

  private async handleError(response: Response): Promise<never> {
    let message = 'Unknown error';
    let code: string | undefined;

    try {
      const errorBody = await response.json();
      message = errorBody.message || message;
      code = errorBody.code;
    } catch {
      message = response.statusText;
    }

    const error = new Error(message) as StorageError;
    error.statusCode = response.status;
    error.code = code;
    throw error;
  }
}

export class StorageApiError extends Error implements StorageError {
  constructor(
    message: string,
    public statusCode: number,
    public code?: string
  ) {
    super(message);
    this.name = 'StorageApiError';
  }
}
