/**
 * Synor Compute Client
 */

import type {
  SynorConfig,
  BalancingStrategy,
  MatMulOptions,
  Conv2dOptions,
  AttentionOptions,
  InferenceOptions,
  JobResult,
  PricingInfo,
  ProcessorType,
} from './types';
import { Tensor } from './tensor';
import { ComputeJob } from './job';

const DEFAULT_ENDPOINT = 'https://compute.synor.cc/api/v1';

/**
 * Main Synor Compute client.
 *
 * @example
 * ```ts
 * const compute = new SynorCompute({ apiKey: 'sk_...' });
 *
 * // Run a matrix multiplication
 * const result = await compute.matmul({
 *   a: [[1, 2], [3, 4]],
 *   b: [[5, 6], [7, 8]]
 * });
 * ```
 */
export class SynorCompute {
  private config: Required<SynorConfig>;

  constructor(config: SynorConfig) {
    this.config = {
      apiKey: config.apiKey,
      endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
      strategy: config.strategy ?? 'balanced',
      precision: config.precision ?? 'fp32',
      timeout: config.timeout ?? 30000,
      debug: config.debug ?? false,
    };
  }

  /**
   * Matrix multiplication on distributed compute.
   */
  async matmul(options: MatMulOptions): Promise<JobResult<Tensor>> {
    const job = await this.submitJob('matmul', {
      a: Tensor.from(options.a).serialize(),
      b: Tensor.from(options.b).serialize(),
      precision: options.precision ?? this.config.precision,
      processor: options.processor,
      priority: options.priority ?? 'normal',
    });

    return job.wait();
  }

  /**
   * 2D Convolution on distributed compute.
   */
  async conv2d(options: Conv2dOptions): Promise<JobResult<Tensor>> {
    const job = await this.submitJob('conv2d', {
      input: Tensor.from(options.input).serialize(),
      kernel: Tensor.from(options.kernel).serialize(),
      stride: options.stride ?? 1,
      padding: options.padding ?? 0,
      precision: options.precision ?? this.config.precision,
    });

    return job.wait();
  }

  /**
   * Self-attention on distributed compute.
   */
  async attention(options: AttentionOptions): Promise<JobResult<Tensor>> {
    const job = await this.submitJob(options.flash ? 'flash_attention' : 'self_attention', {
      query: Tensor.from(options.query).serialize(),
      key: Tensor.from(options.key).serialize(),
      value: Tensor.from(options.value).serialize(),
      num_heads: options.numHeads,
      precision: options.precision ?? this.config.precision,
    });

    return job.wait();
  }

  /**
   * Run inference on a model.
   */
  async inference(options: InferenceOptions): Promise<JobResult<string>> {
    const job = await this.submitJob('inference', {
      model: options.model,
      input: typeof options.input === 'string'
        ? options.input
        : Tensor.from(options.input).serialize(),
      max_tokens: options.maxTokens ?? 256,
      temperature: options.temperature ?? 0.7,
      top_k: options.topK ?? 50,
      strategy: options.strategy ?? this.config.strategy,
    });

    if (options.stream) {
      return this.streamJob(job);
    }

    return job.wait();
  }

  /**
   * Get current pricing for different processor types.
   */
  async getPricing(): Promise<PricingInfo[]> {
    const response = await this.request('GET', '/pricing');
    return response.pricing;
  }

  /**
   * Get pricing for a specific processor type.
   */
  async getProcessorPricing(processor: ProcessorType): Promise<PricingInfo> {
    const response = await this.request('GET', `/pricing/${processor}`);
    return response;
  }

  /**
   * Get available compute capacity.
   */
  async getCapacity(): Promise<{
    totalGflops: number;
    availableGflops: number;
    processorCounts: Record<ProcessorType, number>;
  }> {
    const response = await this.request('GET', '/capacity');
    return response;
  }

  /**
   * Set the default balancing strategy.
   */
  setStrategy(strategy: BalancingStrategy): void {
    this.config.strategy = strategy;
  }

  /**
   * Submit a generic compute job.
   */
  async submitJob(operation: string, params: Record<string, unknown>): Promise<ComputeJob> {
    const response = await this.request('POST', '/jobs', {
      operation,
      params,
      strategy: this.config.strategy,
    });

    return new ComputeJob(response.job_id, this);
  }

  /**
   * Get job status.
   */
  async getJobStatus(jobId: string): Promise<JobResult> {
    const response = await this.request('GET', `/jobs/${jobId}`);
    return response;
  }

  /**
   * Cancel a job.
   */
  async cancelJob(jobId: string): Promise<void> {
    await this.request('DELETE', `/jobs/${jobId}`);
  }

  /**
   * Stream job results.
   */
  private async streamJob<T>(job: ComputeJob): Promise<JobResult<T>> {
    // In a full implementation, this would use SSE or WebSocket
    return job.wait();
  }

  /**
   * Make an API request.
   */
  async request(method: string, path: string, body?: unknown): Promise<any> {
    const url = `${this.config.endpoint}${path}`;

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

    const response = await fetch(url, {
      method,
      headers: {
        'Authorization': `Bearer ${this.config.apiKey}`,
        'Content-Type': 'application/json',
      },
      body: body ? JSON.stringify(body) : undefined,
      signal: AbortSignal.timeout(this.config.timeout),
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({ message: response.statusText }));
      throw new SynorError(error.message || 'Request failed', response.status);
    }

    return response.json();
  }
}

/**
 * Synor SDK error.
 */
export class SynorError extends Error {
  constructor(
    message: string,
    public statusCode?: number,
    public code?: string
  ) {
    super(message);
    this.name = 'SynorError';
  }
}
