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 { UpdateTokenInput } from '../../dto/admin-token.dto';

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

  async execute(tokenId: string, input: UpdateTokenInput): Promise<Token> {
    const existing = await this.tokenRepository.findById(tokenId);
    if (!existing) {
      throw new NotFoundError('Token', tokenId);
    }

    const networkId = input.networkId ?? existing.networkId;
    const network = await this.networkRepository.findById(networkId);
    if (!network) {
      throw new NotFoundError('Network', networkId);
    }

    const isNative = input.isNative ?? existing.isNative;
    const address = input.address !== undefined ? input.address : existing.address;

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

    const name = input.name ?? existing.name;
    const symbol = input.symbol ?? existing.symbol;
    const code = input.code ?? existing.code;

    let thumbnail = existing.thumbnail;
    if (input.thumbnail !== undefined && input.thumbnail !== existing.thumbnail) {
      thumbnail = input.thumbnail
        ? await this.imageStorage.downloadAndStore('token', existing.id, input.thumbnail, {
            name,
            symbol,
          })
        : null;
    }

    const info = await this.resolveTokenInfo(existing.info, input, { name, symbol, code });

    const updated = new Token(
      existing.id,
      isNative,
      isNative ? null : address,
      networkId,
      thumbnail,
      name,
      symbol,
      input.decimals ?? existing.decimals,
      code,
      info,
      input.isActive ?? existing.isActive,
      input.minimumSwapValue !== undefined ? input.minimumSwapValue : existing.minimumSwapValue,
      existing.mcap,
      existing.createdAt,
      new Date(),
    );

    return this.tokenRepository.update(updated);
  }

  private async resolveTokenInfo(
    existingInfo: string | null,
    input: UpdateTokenInput,
    token: { name: string; symbol: string; code: string },
  ): Promise<string | null> {
    if (input.info !== undefined) {
      return input.info;
    }

    if (input.cmcId === undefined && input.code === undefined) {
      return existingInfo;
    }

    try {
      const coin = input.cmcId
        ? await this.coinMarketCapClient.getCoin(input.cmcId)
        : await this.coinMarketCapClient.getCoinInfoBySlug(token.code);

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

    return existingInfo;
  }
}
