/**
 * Synor Economics SDK Client
 * Pricing, billing, staking, and discount management.
 */

import {
  EconomicsConfig,
  ServiceType,
  UsageMetrics,
  Price,
  UsagePlan,
  CostEstimate,
  BillingPeriod,
  Usage,
  Invoice,
  AccountBalance,
  StakeReceipt,
  UnstakeReceipt,
  StakingRewards,
  StakeInfo,
  StakeOptions,
  Discount,
  PriceResponse,
  InvoicesResponse,
  StakesResponse,
  DiscountsResponse,
} from './types';

const DEFAULT_ENDPOINT = 'https://economics.synor.io/v1';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;

export class SynorEconomics {
  private config: Required<EconomicsConfig>;
  private closed = false;

  constructor(config: EconomicsConfig) {
    this.config = {
      apiKey: config.apiKey,
      endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
      timeout: config.timeout ?? DEFAULT_TIMEOUT,
      retries: config.retries ?? DEFAULT_RETRIES,
      debug: config.debug ?? false,
    };
  }

  // ==================== Pricing Operations ====================

  /**
   * Get price for a service based on usage.
   */
  async getPrice(service: ServiceType, usage: UsageMetrics): Promise<Price> {
    const response = await this.post<PriceResponse>('/pricing/calculate', {
      service,
      usage,
    });
    return response.price;
  }

  /**
   * Estimate cost for a usage plan.
   */
  async estimateCost(plan: UsagePlan): Promise<CostEstimate> {
    return await this.post<CostEstimate>('/pricing/estimate', plan);
  }

  /**
   * Get pricing tiers for a service.
   */
  async getPricingTiers(service: ServiceType): Promise<Price[]> {
    const response = await this.get<{ tiers: Price[] }>(`/pricing/${service}/tiers`);
    return response.tiers ?? [];
  }

  // ==================== Usage & Billing Operations ====================

  /**
   * Get usage for a billing period.
   */
  async getUsage(period?: BillingPeriod): Promise<Usage> {
    const params = period ? `?period=${period}` : '';
    return await this.get<Usage>(`/usage${params}`);
  }

  /**
   * Get usage history.
   */
  async getUsageHistory(limit?: number, offset?: number): Promise<Usage[]> {
    const params = new URLSearchParams();
    if (limit) params.set('limit', limit.toString());
    if (offset) params.set('offset', offset.toString());
    const query = params.toString() ? `?${params.toString()}` : '';
    const response = await this.get<{ usage: Usage[] }>(`/usage/history${query}`);
    return response.usage ?? [];
  }

  /**
   * Get invoices.
   */
  async getInvoices(): Promise<Invoice[]> {
    const response = await this.get<InvoicesResponse>('/invoices');
    return response.invoices ?? [];
  }

  /**
   * Get a specific invoice.
   */
  async getInvoice(invoiceId: string): Promise<Invoice> {
    return await this.get<Invoice>(`/invoices/${encodeURIComponent(invoiceId)}`);
  }

  /**
   * Pay an invoice.
   */
  async payInvoice(invoiceId: string, paymentMethod?: string): Promise<Invoice> {
    return await this.post<Invoice>(`/invoices/${encodeURIComponent(invoiceId)}/pay`, {
      paymentMethod,
    });
  }

  /**
   * Get account balance.
   */
  async getBalance(): Promise<AccountBalance> {
    return await this.get<AccountBalance>('/balance');
  }

  /**
   * Add funds to account.
   */
  async addFunds(amount: string, paymentMethod: string): Promise<AccountBalance> {
    return await this.post<AccountBalance>('/balance/deposit', {
      amount,
      paymentMethod,
    });
  }

  // ==================== Staking Operations ====================

  /**
   * Stake tokens.
   */
  async stake(amount: string, options?: StakeOptions): Promise<StakeReceipt> {
    const body: Record<string, unknown> = { amount };
    if (options?.validator) body.validator = options.validator;
    if (options?.autoCompound !== undefined) body.autoCompound = options.autoCompound;
    if (options?.lockDuration) body.lockDuration = options.lockDuration;
    return await this.post<StakeReceipt>('/staking/stake', body);
  }

  /**
   * Unstake tokens.
   */
  async unstake(stakeId: string): Promise<UnstakeReceipt> {
    return await this.post<UnstakeReceipt>(`/staking/stakes/${encodeURIComponent(stakeId)}/unstake`, {});
  }

  /**
   * Get staking rewards.
   */
  async getStakingRewards(): Promise<StakingRewards> {
    return await this.get<StakingRewards>('/staking/rewards');
  }

  /**
   * Claim staking rewards.
   */
  async claimRewards(stakeId?: string): Promise<StakingRewards> {
    const path = stakeId
      ? `/staking/stakes/${encodeURIComponent(stakeId)}/claim`
      : '/staking/rewards/claim';
    return await this.post<StakingRewards>(path, {});
  }

  /**
   * List active stakes.
   */
  async listStakes(): Promise<StakeInfo[]> {
    const response = await this.get<StakesResponse>('/staking/stakes');
    return response.stakes ?? [];
  }

  /**
   * Get stake details.
   */
  async getStake(stakeId: string): Promise<StakeInfo> {
    return await this.get<StakeInfo>(`/staking/stakes/${encodeURIComponent(stakeId)}`);
  }

  /**
   * Get current APY for staking.
   */
  async getStakingApy(): Promise<{ apy: string; minLockDuration: number; maxLockDuration: number }> {
    return await this.get('/staking/apy');
  }

  // ==================== Discount Operations ====================

  /**
   * Apply a discount code.
   */
  async applyDiscount(code: string): Promise<Discount> {
    return await this.post<Discount>('/discounts/apply', { code });
  }

  /**
   * Get available discounts.
   */
  async getAvailableDiscounts(): Promise<Discount[]> {
    const response = await this.get<DiscountsResponse>('/discounts');
    return response.discounts ?? [];
  }

  /**
   * Get active discounts on account.
   */
  async getActiveDiscounts(): Promise<Discount[]> {
    const response = await this.get<DiscountsResponse>('/discounts/active');
    return response.discounts ?? [];
  }

  /**
   * Remove a discount.
   */
  async removeDiscount(code: string): Promise<void> {
    await this.delete(`/discounts/${encodeURIComponent(code)}`);
  }

  // ==================== Lifecycle ====================

  /**
   * Health check.
   */
  async healthCheck(): Promise<boolean> {
    try {
      const response = await this.get<{ status: string }>('/health');
      return response.status === 'healthy';
    } catch {
      return false;
    }
  }

  /**
   * Check if client is closed.
   */
  get isClosed(): boolean {
    return this.closed;
  }

  /**
   * Close the client.
   */
  close(): void {
    this.closed = true;
  }

  // ==================== Private HTTP Methods ====================

  private async get<T>(path: string): Promise<T> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'GET',
        headers: this.headers(),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
      return await response.json();
    });
  }

  private async post<T>(path: string, body: unknown): Promise<T> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'POST',
        headers: this.headers(),
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
      return await response.json();
    });
  }

  private async delete(path: string): Promise<void> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'DELETE',
        headers: this.headers(),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
    });
  }

  private headers(): Record<string, string> {
    return {
      'Authorization': `Bearer ${this.config.apiKey}`,
      'Content-Type': 'application/json',
      'X-SDK-Version': 'js/0.1.0',
    };
  }

  private async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.closed) {
      throw new EconomicsError('Client has been closed');
    }

    let lastError: Error | undefined;
    for (let attempt = 0; attempt < this.config.retries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as Error;
        if (this.config.debug) {
          console.log(`Attempt ${attempt + 1} failed:`, error);
        }
        if (attempt < this.config.retries - 1) {
          await this.sleep(1000 * Math.pow(2, attempt));
        }
      }
    }
    throw lastError!;
  }

  private async ensureSuccess(response: Response): Promise<void> {
    if (!response.ok) {
      const body = await response.text();
      let message = `HTTP ${response.status}`;
      let code: string | undefined;
      try {
        const error = JSON.parse(body);
        message = error.message ?? message;
        code = error.code;
      } catch {
        // ignore parse errors
      }
      throw new EconomicsError(message, code, response.status);
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

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

// Re-export types
export * from './types';
