/**
 * Synor Explorer API client.
 * Provides typed methods for all explorer API endpoints.
 * Falls back to mock data when VITE_USE_MOCK=true or backend is unavailable.
 */

import type {
  NetworkStats,
  ExplorerBlock,
  ExplorerTransaction,
  AddressInfo,
  UTXO,
  DagVisualization,
  PaginatedResponse,
  SearchResult,
  HealthStatus,
  ApiError,
  GasEstimateRequest,
  GasEstimateResponse,
  GasCosts,
} from './types';
import {
  mockStats,
  mockBlocks,
  mockBlocksResponse,
  mockDag,
  mockTransaction,
  mockAddress,
  mockUtxos,
} from '../mocks/api';

const API_BASE = '/api/v1';

/**
 * Check if we're in a production build
 * VITE_MODE is set by Vite during build
 */
const isProduction = import.meta.env.PROD;

/**
 * Check if mock mode is enabled.
 * SECURITY: Mock mode is NEVER allowed in production builds.
 * This prevents users from enabling fake data via localStorage.
 */
const isMockMode = (): boolean => {
  // Production builds NEVER use mock mode
  if (isProduction) {
    return false;
  }

  // Development only: allow localStorage override for testing
  if (typeof window !== 'undefined' && localStorage.getItem('useMockApi') === 'true') {
    return true;
  }

  // Development only: allow env var override
  return import.meta.env.VITE_USE_MOCK === 'true';
};

class ApiClient {
  private useMock = isMockMode();

  /**
   * Enable or disable mock mode (development only).
   * SECURITY: This method is disabled in production builds.
   */
  enableMock(enable: boolean): void {
    // SECURITY: Never allow mock mode in production
    if (isProduction) {
      console.warn('[API] Mock mode cannot be enabled in production builds');
      return;
    }

    this.useMock = enable;
    if (typeof window !== 'undefined') {
      localStorage.setItem('useMockApi', String(enable));
    }
  }

  private async fetch<T>(endpoint: string, options?: RequestInit): Promise<T> {
    const url = endpoint.startsWith('http') ? endpoint : `${API_BASE}${endpoint}`;

    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options?.headers,
      },
    });

    if (!response.ok) {
      const error: ApiError = await response.json().catch(() => ({
        error: `HTTP ${response.status}: ${response.statusText}`,
        code: response.status,
      }));
      throw new Error(error.error);
    }

    return response.json();
  }

  // Health & Status
  async getHealth(): Promise<HealthStatus> {
    if (this.useMock) {
      return { healthy: true, rpcConnected: true };
    }
    return this.fetch<HealthStatus>('/health');
  }

  async getStats(): Promise<NetworkStats> {
    if (this.useMock) {
      return mockStats as NetworkStats;
    }
    return this.fetch<NetworkStats>('/stats');
  }

  // Blocks
  async getBlocks(page = 1, limit = 25): Promise<PaginatedResponse<ExplorerBlock>> {
    if (this.useMock) {
      // Generate blocks dynamically for the requested page
      const total = mockBlocksResponse.total;
      const totalPages = Math.ceil(total / limit);
      const startIndex = (page - 1) * limit;

      // Generate mock blocks for this specific page
      const data = Array.from({ length: Math.min(limit, total - startIndex) }, (_, i) => {
        const blockIndex = startIndex + i;
        const seed = total - blockIndex;
        const hash = this.generateMockHash(seed);
        return {
          hash,
          version: 1,
          parentHashes: [this.generateMockHash(seed - 1)],
          timestamp: Date.now() - blockIndex * 100,
          timestampHuman: new Date(Date.now() - blockIndex * 100).toISOString(),
          bits: 486604799,
          nonce: (seed * 12345) % 1000000000,
          daaScore: total - blockIndex,
          blueScore: total - blockIndex,
          blueWork: '0x' + this.generateMockHash(blockIndex).slice(0, 16),
          difficulty: 1234567890.5,
          transactionCount: ((seed * 7) % 50) + 1,
          isChainBlock: true,
          childrenHashes: blockIndex === 0 ? [] : [this.generateMockHash(seed + 1)],
          mergeSetBlues: [],
          mergeSetReds: [],
        } as ExplorerBlock;
      });

      return {
        data,
        page,
        limit,
        total,
        totalPages,
        hasNext: page < totalPages,
        hasPrev: page > 1,
      };
    }
    return this.fetch<PaginatedResponse<ExplorerBlock>>(
      `/blocks?page=${page}&limit=${limit}`
    );
  }

  private generateMockHash(seed: number): string {
    // Use a better hash generation that ensures uniqueness
    // Convert seed to hex and pad/extend to 64 chars
    const seedHex = Math.abs(seed).toString(16).padStart(8, '0');
    const chars = '0123456789abcdef';
    let hash = '';

    // Create a deterministic but unique hash based on seed
    for (let i = 0; i < 64; i++) {
      const charIndex = (seed * 31 + i * 7 + Math.floor(i / 8) * seed) % 16;
      hash += chars[Math.abs(charIndex)];
    }

    // Embed the seed hex in the middle to guarantee uniqueness
    return hash.slice(0, 28) + seedHex + hash.slice(36);
  }

  async getBlock(hash: string, includeTxs = true): Promise<ExplorerBlock> {
    if (this.useMock) {
      const block = mockBlocks.find(b => b.hash === hash) || mockBlocks[0];
      return {
        ...block,
        transactions: includeTxs ? [mockTransaction as ExplorerTransaction] : undefined,
      } as ExplorerBlock;
    }
    return this.fetch<ExplorerBlock>(
      `/blocks/${hash}?include_txs=${includeTxs}`
    );
  }

  async getTips(): Promise<string[]> {
    if (this.useMock) {
      return mockBlocks.slice(0, 3).map(b => b.hash);
    }
    return this.fetch<string[]>('/tips');
  }

  // Transactions
  async getTransaction(txId: string): Promise<ExplorerTransaction> {
    if (this.useMock) {
      return { ...mockTransaction, id: txId, hash: txId } as ExplorerTransaction;
    }
    return this.fetch<ExplorerTransaction>(`/tx/${txId}`);
  }

  async getMempool(page = 1, limit = 25): Promise<PaginatedResponse<ExplorerTransaction>> {
    if (this.useMock) {
      return {
        data: [mockTransaction as ExplorerTransaction],
        page,
        limit,
        total: 1,
        totalPages: 1,
        hasNext: false,
        hasPrev: false,
      };
    }
    return this.fetch<PaginatedResponse<ExplorerTransaction>>(
      `/mempool?page=${page}&limit=${limit}`
    );
  }

  // Addresses
  async getAddress(address: string): Promise<AddressInfo> {
    if (this.useMock) {
      return { ...mockAddress, address } as AddressInfo;
    }
    return this.fetch<AddressInfo>(`/address/${address}`);
  }

  async getAddressUtxos(address: string): Promise<UTXO[]> {
    if (this.useMock) {
      return mockUtxos as UTXO[];
    }
    return this.fetch<UTXO[]>(`/address/${address}/utxos`);
  }

  // DAG
  async getDag(depth = 10): Promise<DagVisualization> {
    if (this.useMock) {
      return mockDag as DagVisualization;
    }
    return this.fetch<DagVisualization>(`/dag?depth=${depth}`);
  }

  // Search
  async search(query: string): Promise<SearchResult> {
    if (this.useMock) {
      // Mock search - detect type from query
      if (query.startsWith('synor1')) {
        return { resultType: 'address', value: query, redirectUrl: `/address/${query}` };
      }
      if (query.length === 64) {
        // Could be block or tx - default to block
        return { resultType: 'block', value: query, redirectUrl: `/block/${query}` };
      }
      throw new Error('No matching block, transaction, or address found');
    }
    return this.fetch<SearchResult>(`/search?q=${encodeURIComponent(query)}`);
  }

  // Gas Estimation
  async estimateGas(request: GasEstimateRequest): Promise<GasEstimateResponse> {
    if (this.useMock) {
      // Mock gas estimation
      const gasUsed = 21000 + (request.data ? request.data.length * 16 : 0);
      const gasLimitRecommended = Math.ceil(gasUsed * 1.2);
      const estimatedFee = gasLimitRecommended * 1; // 1 sompi per gas
      return {
        gasUsed,
        gasLimitRecommended,
        estimatedFee,
        estimatedFeeHuman: `${(estimatedFee / 100_000_000).toFixed(8)} SYN`,
      };
    }
    return this.fetch<GasEstimateResponse>('/estimate-gas', {
      method: 'POST',
      body: JSON.stringify(request),
    });
  }

  async getGasCosts(): Promise<GasCosts> {
    if (this.useMock) {
      return {
        transfer: 21000,
        contractDeployBase: 32000,
        contractCallBase: 21000,
        storage: { sstore: 20000, sload: 2100 },
        memory: { perWord: 3 },
        signature: { ed25519: 3000, dilithium: 15000 },
      };
    }
    return this.fetch<GasCosts>('/gas-costs');
  }
}

export const api = new ApiClient();
export default api;
