import { v4 as uuidv4 } from 'uuid';
import { Token } from '../../../domain/entities/token';
import { NotFoundError } from '../../../domain/errors/not-found-error';
import { ValidationError } from '../../../domain/errors/validation-error';
import { ITokenRepository } from '../../../domain/repositories/token-repository';
import { INetworkRepository } from '../../../domain/repositories/network-repository';
import { IImageStorage } from '../../../application/ports/image-storage';
import { ILogger } from '../../../application/ports/logger';
import { CoinMarketCapClient } from '../../../infrastructure/external/coinmarketcap-client';
import { CreateTokenInput } from '../../dto/admin-token.dto';

export class CreateTokenUseCase {
  constructor(
    private readonly tokenRepository: ITokenRepository,
    private readonly networkRepository: INetworkRepository,
    private readonly imageStorage: IImageStorage,
    private readonly coinMarketCapClient: CoinMarketCapClient,
    private readonly logger: ILogger,
  ) {}

  async execute(input: CreateTokenInput): Promise<Token> {
    const network = await this.networkRepository.findById(input.networkId);
    if (!network) {
      throw new NotFoundError('Network', input.networkId);
    }

    if (!input.isNative && !input.address) {
      throw new ValidationError('Non-native tokens must have a contract address', {
        address: ['Contract address is required'],
      });
    }

    const id = uuidv4();
    const thumbnail = input.thumbnail
      ? await this.imageStorage.downloadAndStore('token', id, input.thumbnail, {
          name: input.name,
          symbol: input.symbol,
        })
      : null;

    const info = await this.resolveTokenInfo(input);

    const token = Token.create(
      id,
      input.isNative,
      input.isNative ? null : (input.address ?? null),
      input.networkId,
      thumbnail,
      input.name,
      input.symbol,
      input.decimals,
      input.code,
      info,
      input.isActive,
      input.minimumSwapValue ?? null,
      null,
    );

    return this.tokenRepository.save(token);
  }

  private async resolveTokenInfo(input: CreateTokenInput): Promise<string | null> {
    try {
      const coin = input.cmcId
        ? await this.coinMarketCapClient.getCoin(input.cmcId)
        : await this.coinMarketCapClient.getCoinInfoBySlug(input.code);

      if (coin?.description) {
        return coin.description;
      }
    } catch (error) {
      this.logger.warn('Failed to fetch token info from CoinMarketCap, using fallback', {
        code: input.code,
        cmcId: input.cmcId,
        error,
      });
    }

    return input.info ?? null;
  }
}
