import axios, { AxiosError } from 'axios';
import fs from 'fs/promises';
import path from 'path';
import { IImageStorage, ImageEntityType, ImageResult, ThumbnailMetadata } from '../../application/ports/image-storage';
import { IThumbnailResolver } from '../../application/ports/thumbnail-resolver';

const MIME_TO_EXTENSION: Record<string, string> = {
  'image/png': 'png',
  'image/jpeg': 'jpg',
  'image/jpg': 'jpg',
  'image/svg+xml': 'svg',
  'image/gif': 'gif',
  'image/webp': 'webp',
  'image/avif': 'avif',
  'image/x-icon': 'ico',
  'image/vnd.microsoft-icon': 'ico',
};

const EXTENSION_TO_MIME: Record<string, string> = {
  png: 'image/png',
  jpg: 'image/jpeg',
  jpeg: 'image/jpeg',
  svg: 'image/svg+xml',
  gif: 'image/gif',
  webp: 'image/webp',
  avif: 'image/avif',
  ico: 'image/x-icon',
  bin: 'application/octet-stream',
};

const FOLDER_NAME: Record<ImageEntityType, string> = {
  network: 'networks',
  token: 'tokens',
};

const isExternalUrl = (url: string): boolean => {
  const lower = url.toLowerCase();
  return lower.startsWith('http://') || lower.startsWith('https://');
};

const isNotFoundError = (error: unknown): boolean => {
  if (!axios.isAxiosError(error)) {
    return false;
  }
  const axiosError = error as AxiosError;
  return axiosError.response?.status === 404;
};

export class FileImageStorageService implements IImageStorage {
  constructor(
    private readonly publicBaseUrl: string,
    private readonly imagesDir: string,
    private readonly thumbnailResolver?: IThumbnailResolver,
  ) {}

  async downloadAndStore(
    type: ImageEntityType,
    id: string,
    url: string,
    metadata?: ThumbnailMetadata,
  ): Promise<string | null> {
    if (!isExternalUrl(url)) {
      return url;
    }

    try {
      return await this.downloadImage(type, id, url);
    } catch (error) {
      if (!isNotFoundError(error) || !this.thumbnailResolver || !metadata) {
        throw error;
      }
    }

    const fallbackUrl = await this.thumbnailResolver.resolve(type, metadata);
    if (!fallbackUrl) {
      return null;
    }

    return this.downloadImage(type, id, fallbackUrl);
  }

  async readImage(type: ImageEntityType, id: string): Promise<ImageResult | null> {
    const folder = path.resolve(this.imagesDir, FOLDER_NAME[type]);

    try {
      await fs.access(folder);
    } catch {
      return null;
    }

    const files = await fs.readdir(folder);
    const filename = files.find((file) => file.startsWith(`${id}.`));
    if (!filename) {
      return null;
    }

    const ext = filename.split('.').pop()?.toLowerCase() ?? 'bin';
    const contentType = EXTENSION_TO_MIME[ext] ?? 'application/octet-stream';
    const buffer = await fs.readFile(path.join(folder, filename));

    return { buffer, contentType };
  }

  private async downloadImage(type: ImageEntityType, id: string, url: string): Promise<string> {
    const folder = path.resolve(this.imagesDir, FOLDER_NAME[type]);
    await fs.mkdir(folder, { recursive: true });

    const response = await axios.get(url, {
      responseType: 'arraybuffer',
      timeout: 30000,
      maxRedirects: 5,
      validateStatus: (status) => status < 400,
    });

    const contentType = response.headers['content-type']?.toString() ?? 'application/octet-stream';
    const extension = MIME_TO_EXTENSION[contentType.split(';')[0].trim().toLowerCase()] ?? 'bin';

    const filename = `${id}.${extension}`;
    const filePath = path.join(folder, filename);
    await fs.writeFile(filePath, Buffer.from(response.data));

    const publicUrl = `${this.publicBaseUrl}/images/${FOLDER_NAME[type]}/${filename}`;
    return publicUrl;
  }
}
