import { getLogger } from '@stock-bot/logger';
import type { IServiceContainer } from '@stock-bot/types';
import {
  CreateExchangeRequest,
  CreateProviderMappingRequest,
  Exchange,
  ExchangeStats,
  ExchangeWithMappings,
  ProviderExchange,
  ProviderMapping,
  UpdateExchangeRequest,
  UpdateProviderMappingRequest,
} from '../types/exchange.types';

const logger = getLogger('exchange-service');

export class ExchangeService {
  constructor(private container: IServiceContainer) {}

  private get postgresClient() {
    return this.container.postgres;
  }

  private get mongoClient() {
    return this.container.mongodb;
  }

  // Exchanges
  async getAllExchanges(): Promise<ExchangeWithMappings[]> {
    const exchangesQuery = `
      SELECT 
        e.id,
        e.code,
        e.name,
        e.country,
        e.currency,
        e.active,
        e.visible,
        e.created_at,
        e.updated_at,
        COUNT(pem.id) as provider_mapping_count,
        COUNT(CASE WHEN pem.active = true THEN 1 END) as active_mapping_count,
        COUNT(CASE WHEN pem.verified = true THEN 1 END) as verified_mapping_count,
        STRING_AGG(DISTINCT pem.provider, ', ') as providers
      FROM exchanges e
      LEFT JOIN provider_exchange_mappings pem ON e.id = pem.master_exchange_id
      WHERE e.visible = true
      GROUP BY e.id, e.code, e.name, e.country, e.currency, e.active, e.visible, e.created_at, e.updated_at
      ORDER BY e.code
    `;

    const exchangesResult = await this.postgresClient.query(exchangesQuery);

    // Get all provider mappings
    const mappingsQuery = `
      SELECT 
        pem.*,
        e.code as master_exchange_code,
        e.name as master_exchange_name
      FROM provider_exchange_mappings pem
      JOIN exchanges e ON pem.master_exchange_id = e.id
      WHERE e.visible = true
      ORDER BY pem.master_exchange_id, pem.provider, pem.provider_exchange_code
    `;
    const mappingsResult = await this.postgresClient.query(mappingsQuery);

    // Group mappings by exchange ID
    const mappingsByExchange = mappingsResult.rows.reduce(
      (acc, mapping) => {
        const exchangeId = mapping.master_exchange_id;
        if (!acc[exchangeId]) {
          acc[exchangeId] = [];
        }
        acc[exchangeId].push(mapping);
        return acc;
      },
      {} as Record<string, ProviderMapping[]>
    );

    // Attach mappings to exchanges
    return exchangesResult.rows.map(exchange => ({
      ...exchange,
      provider_mappings: mappingsByExchange[exchange.id] || [],
    }));
  }

  async getExchangeById(
    id: string
  ): Promise<{ exchange: Exchange; provider_mappings: ProviderMapping[] } | null> {
    const exchangeQuery = 'SELECT * FROM exchanges WHERE id = $1 AND visible = true';
    const exchangeResult = await this.postgresClient.query(exchangeQuery, [id]);

    if (exchangeResult.rows.length === 0) {
      return null;
    }

    const mappingsQuery = `
      SELECT 
        pem.*,
        e.code as master_exchange_code,
        e.name as master_exchange_name
      FROM provider_exchange_mappings pem
      JOIN exchanges e ON pem.master_exchange_id = e.id
      WHERE pem.master_exchange_id = $1
      ORDER BY pem.provider, pem.provider_exchange_code
    `;
    const mappingsResult = await this.postgresClient.query(mappingsQuery, [id]);

    return {
      exchange: exchangeResult.rows[0],
      provider_mappings: mappingsResult.rows,
    };
  }

  async createExchange(data: CreateExchangeRequest): Promise<Exchange> {
    const query = `
      INSERT INTO exchanges (code, name, country, currency, active, visible)
      VALUES ($1, $2, $3, $4, $5, true)
      RETURNING *
    `;

    const result = await this.postgresClient.query(query, [
      data.code,
      data.name,
      data.country,
      data.currency,
      data.active,
    ]);

    logger.info('Exchange created', {
      exchangeId: result.rows[0].id,
      code: data.code,
      name: data.name,
    });

    return result.rows[0];
  }

  async updateExchange(id: string, updates: UpdateExchangeRequest): Promise<Exchange | null> {
    const updateFields = [];
    const values = [];
    let paramIndex = 1;

    Object.entries(updates).forEach(([key, value]) => {
      updateFields.push(`${key} = $${paramIndex++}`);
      values.push(value);
    });

    updateFields.push(`updated_at = NOW()`);
    values.push(id);

    const query = `
      UPDATE exchanges 
      SET ${updateFields.join(', ')}
      WHERE id = $${paramIndex}
      RETURNING *
    `;

    const result = await this.postgresClient.query(query, values);

    if (result.rows.length === 0) {
      return null;
    }

    // If hiding an exchange, delete its provider mappings
    if (updates.visible === false) {
      await this.deleteProviderMappingsByExchangeId(id);
    }

    logger.info('Exchange updated', { exchangeId: id, updates });

    return result.rows[0];
  }

  // Provider Mappings
  async getAllProviderMappings(): Promise<ProviderMapping[]> {
    const query = `
      SELECT 
        pem.*,
        e.code as master_exchange_code,
        e.name as master_exchange_name,
        e.active as master_exchange_active
      FROM provider_exchange_mappings pem
      JOIN exchanges e ON pem.master_exchange_id = e.id
      WHERE e.visible = true
      ORDER BY pem.provider, pem.provider_exchange_code
    `;

    const result = await this.postgresClient.query(query);
    return result.rows;
  }

  async getProviderMappingsByProvider(provider: string): Promise<ProviderMapping[]> {
    const query = `
      SELECT 
        pem.*,
        e.code as master_exchange_code,
        e.name as master_exchange_name,
        e.active as master_exchange_active
      FROM provider_exchange_mappings pem
      JOIN exchanges e ON pem.master_exchange_id = e.id
      WHERE pem.provider = $1 AND e.visible = true
      ORDER BY pem.provider_exchange_code
    `;

    const result = await this.postgresClient.query(query, [provider]);
    return result.rows;
  }

  async createProviderMapping(data: CreateProviderMappingRequest): Promise<ProviderMapping> {
    const query = `
      INSERT INTO provider_exchange_mappings 
      (provider, provider_exchange_code, provider_exchange_name, master_exchange_id, 
       country_code, currency, confidence, active, verified, auto_mapped)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, false)
      RETURNING *
    `;

    const result = await this.postgresClient.query(query, [
      data.provider,
      data.provider_exchange_code,
      data.provider_exchange_name,
      data.master_exchange_id,
      data.country_code,
      data.currency,
      data.confidence,
      data.active,
      data.verified,
    ]);

    logger.info('Provider mapping created', {
      provider: data.provider,
      provider_exchange_code: data.provider_exchange_code,
      master_exchange_id: data.master_exchange_id,
    });

    return result.rows[0];
  }

  async updateProviderMapping(
    id: string,
    updates: UpdateProviderMappingRequest
  ): Promise<ProviderMapping | null> {
    const updateFields = [];
    const values = [];
    let paramIndex = 1;

    Object.entries(updates).forEach(([key, value]) => {
      updateFields.push(`${key} = $${paramIndex++}`);
      values.push(value);
    });

    updateFields.push(`updated_at = NOW()`);
    updateFields.push(`auto_mapped = false`); // Mark as manually managed
    values.push(id);

    const query = `
      UPDATE provider_exchange_mappings 
      SET ${updateFields.join(', ')}
      WHERE id = $${paramIndex}
      RETURNING *
    `;

    const result = await this.postgresClient.query(query, values);

    if (result.rows.length === 0) {
      return null;
    }

    logger.info('Provider mapping updated', { mappingId: id, updates });

    return result.rows[0];
  }

  async deleteProviderMappingsByExchangeId(exchangeId: string): Promise<number> {
    const query = 'DELETE FROM provider_exchange_mappings WHERE master_exchange_id = $1';
    const result = await this.postgresClient.query(query, [exchangeId]);

    logger.info('Deleted provider mappings for hidden exchange', {
      exchangeId,
      deletedMappings: result.rowCount,
    });

    return result.rowCount || 0;
  }

  // Providers and Statistics
  async getProviders(): Promise<string[]> {
    const query = `
      SELECT DISTINCT provider
      FROM provider_exchange_mappings
      ORDER BY provider
    `;

    const result = await this.postgresClient.query(query);
    return result.rows.map(row => row.provider);
  }

  async getExchangeStats(): Promise<ExchangeStats> {
    const query = `
      SELECT 
        (SELECT COUNT(*) FROM exchanges WHERE visible = true) as total_exchanges,
        (SELECT COUNT(*) FROM exchanges WHERE active = true AND visible = true) as active_exchanges,
        (SELECT COUNT(DISTINCT country) FROM exchanges WHERE visible = true) as countries,
        (SELECT COUNT(DISTINCT currency) FROM exchanges WHERE visible = true) as currencies,
        (SELECT COUNT(*) FROM provider_exchange_mappings pem JOIN exchanges e ON pem.master_exchange_id = e.id WHERE e.visible = true) as total_provider_mappings,
        (SELECT COUNT(*) FROM provider_exchange_mappings pem JOIN exchanges e ON pem.master_exchange_id = e.id WHERE pem.active = true AND e.visible = true) as active_provider_mappings,
        (SELECT COUNT(*) FROM provider_exchange_mappings pem JOIN exchanges e ON pem.master_exchange_id = e.id WHERE pem.verified = true AND e.visible = true) as verified_provider_mappings,
        (SELECT COUNT(DISTINCT provider) FROM provider_exchange_mappings pem JOIN exchanges e ON pem.master_exchange_id = e.id WHERE e.visible = true) as providers
    `;

    const result = await this.postgresClient.query(query);
    return result.rows[0];
  }

  async getUnmappedProviderExchanges(provider: string): Promise<ProviderExchange[]> {
    // Get existing mappings for this provider
    const existingMappingsQuery = `
      SELECT provider_exchange_code
      FROM provider_exchange_mappings
      WHERE provider = $1
    `;
    const existingMappings = await this.postgresClient.query(existingMappingsQuery, [provider]);
    const mappedCodes = new Set(existingMappings.rows.map(row => row.provider_exchange_code));

    const db = this.mongoClient.getDatabase();
    let providerExchanges: ProviderExchange[] = [];

    switch (provider) {
      case 'eod': {
        const eodExchanges = await db.collection('eodExchanges').find({ active: true }).toArray();
        providerExchanges = eodExchanges
          .filter(exchange => !mappedCodes.has(exchange.Code))
          .map(exchange => ({
            provider_exchange_code: exchange.Code,
            provider_exchange_name: exchange.Name,
            country_code: exchange.CountryISO2,
            currency: exchange.Currency,
            symbol_count: null,
          }));
        break;
      }

      case 'ib': {
        const ibExchanges = await db.collection('ibExchanges').find({}).toArray();
        providerExchanges = ibExchanges
          .filter(exchange => !mappedCodes.has(exchange.exchange_id))
          .map(exchange => ({
            provider_exchange_code: exchange.exchange_id,
            provider_exchange_name: exchange.name,
            country_code: exchange.country_code,
            currency: null,
            symbol_count: null,
          }));
        break;
      }

      case 'qm': {
        const qmExchanges = await db.collection('qmExchanges').find({}).toArray();
        providerExchanges = qmExchanges
          .filter(exchange => !mappedCodes.has(exchange.exchangeCode))
          .map(exchange => ({
            provider_exchange_code: exchange.exchangeCode,
            provider_exchange_name: exchange.name,
            country_code: exchange.countryCode,
            currency: exchange.countryCode === 'CA' ? 'CAD' : 'USD',
            symbol_count: null,
          }));
        break;
      }

      default:
        throw new Error(`Unknown provider: ${provider}`);
    }

    return providerExchanges;
  }
}

// Export function to create service instance with container
export function createExchangeService(container: IServiceContainer): ExchangeService {
  return new ExchangeService(container);
}
