import {
  Injectable,
  PLATFORM_ID,
  inject
} from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

export interface ReleaseAsset {
  name: string;
  browser_download_url: string;
  size: number;
}

export interface Release {
  tag_name: string;
  name: string;
  published_at: string;
  body: string;
  assets: ReleaseAsset[];
  html_url: string;
}

export type OsKey = 'windows' | 'macos' | 'linux' | 'linuxDebian' | 'archive' | 'web' | 'other';

export interface DetectedOS {
  key: 'windows' | 'macos' | 'linux' | 'linuxDebian';
  icon: string | null;
  filePattern: RegExp;
  ymlFile: string;
}

const WINDOWS_SUFFIXES = ['.exe', '.msi'];
const WINDOWS_HINTS = [
  'setup',
  'win',
  'windows'
];
const MAC_SUFFIXES = ['.dmg', '.pkg'];
const MAC_HINTS = [
  'mac',
  'macos',
  'osx',
  'darwin'
];
const LINUX_SUFFIXES = [
  '.appimage',
  '.deb',
  '.rpm'
];
const LINUX_HINTS = ['linux'];
const ARCHIVE_SUFFIXES = [
  '.zip',
  '.tar',
  '.tar.gz',
  '.tgz',
  '.tar.xz',
  '.7z',
  '.rar'
];
const DIRECT_RELEASES_API_URL = 'https://git.azaaxin.com/api/v1/repos/myxelium/Toju/releases';
const PROXY_RELEASES_API_URL = '/api/releases';

function matchesAssetPattern(name: string, suffixes: string[], hints: string[] = []): boolean {
  if (suffixes.some((suffix) => name.endsWith(suffix))) {
    return true;
  }

  const tokens = name.split(/[^a-z0-9]+/).filter(Boolean);

  return hints.some((hint) => tokens.includes(hint));
}

@Injectable({ providedIn: 'root' })
export class ReleaseService {
  private readonly platformId = inject(PLATFORM_ID);

  private cachedReleases: Release[] | null = null;
  private fetchPromise: Promise<Release[]> | null = null;

  detectOS(): DetectedOS {
    if (!isPlatformBrowser(this.platformId)) {
      return {
        key: 'linux',
        icon: null,
        filePattern: /\.AppImage$/i,
        ymlFile: 'latest-linux.yml'
      };
    }

    const userAgent = navigator.userAgent.toLowerCase();

    if (userAgent.includes('win')) {
      return {
        key: 'windows',
        icon: null,
        filePattern: /\.exe$/i,
        ymlFile: 'latest.yml'
      };
    }

    if (userAgent.includes('mac')) {
      return {
        key: 'macos',
        icon: null,
        filePattern: /\.dmg$/i,
        ymlFile: 'latest-mac.yml'
      };
    }

    const isUbuntuDebian = userAgent.includes('ubuntu') || userAgent.includes('debian');

    if (isUbuntuDebian) {
      return {
        key: 'linuxDebian',
        icon: null,
        filePattern: /\.deb$/i,
        ymlFile: 'latest-linux.yml'
      };
    }

    return {
      key: 'linux',
      icon: null,
      filePattern: /\.AppImage$/i,
      ymlFile: 'latest-linux.yml'
    };
  }

  fetchReleases(): Promise<Release[]> {
    if (this.cachedReleases) {
      return Promise.resolve(this.cachedReleases);
    }

    if (isPlatformServer(this.platformId)) {
      return Promise.resolve([]);
    }

    if (this.fetchPromise) {
      return this.fetchPromise;
    }

    this.fetchPromise = this.fetchReleasesInternal();

    return this.fetchPromise;
  }

  async getLatestRelease(): Promise<Release | null> {
    const releases = await this.fetchReleases();

    return releases.length > 0 ? releases[0] : null;
  }

  async getDownloadUrl(os: DetectedOS): Promise<string | null> {
    const release = await this.getLatestRelease();

    if (!release) {
      return null;
    }

    const matchingAsset = release.assets.find(
      (releaseAsset) => os.filePattern.test(releaseAsset.name)
    );

    return matchingAsset?.browser_download_url ?? null;
  }

  getAssetOSKey(name: string): OsKey {
    const lower = name.toLowerCase();

    if (matchesAssetPattern(lower, WINDOWS_SUFFIXES, WINDOWS_HINTS)) {
      return 'windows';
    }

    if (matchesAssetPattern(lower, MAC_SUFFIXES, MAC_HINTS)) {
      return 'macos';
    }

    if (matchesAssetPattern(lower, LINUX_SUFFIXES, LINUX_HINTS)) {
      return 'linux';
    }

    if (matchesAssetPattern(lower, ARCHIVE_SUFFIXES)) {
      return 'archive';
    }

    if (lower.endsWith('.wasm')) {
      return 'web';
    }

    return 'other';
  }

  formatBytes(bytes: number): string {
    if (bytes === 0) {
      return '0 B';
    }

    const kilobyte = 1024;
    const units = [
      'B',
      'KB',
      'MB',
      'GB'
    ];
    const unitIndex = Math.floor(Math.log(bytes) / Math.log(kilobyte));

    return `${parseFloat((bytes / Math.pow(kilobyte, unitIndex)).toFixed(1))} ${units[unitIndex]}`;
  }

  private async fetchReleasesInternal(): Promise<Release[]> {
    try {
      const data = await this.fetchReleasesFromAvailableEndpoints();

      this.cachedReleases = Array.isArray(data) ? data : [data];

      return this.cachedReleases;
    } catch (error) {
      console.error('Failed to fetch releases:', error);

      return [];
    } finally {
      this.fetchPromise = null;
    }
  }

  private async fetchReleasesFromAvailableEndpoints(): Promise<Release[] | Release> {
    let lastError: unknown = null;

    for (const endpoint of this.getReleaseEndpoints()) {
      try {
        const response = await fetch(endpoint, {
          headers: { Accept: 'application/json' }
        });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }

        return await response.json();
      } catch (error) {
        lastError = error;
      }
    }

    throw lastError instanceof Error
      ? lastError
      : new Error('Failed to fetch releases from all configured endpoints.');
  }

  private getReleaseEndpoints(): string[] {
    return [PROXY_RELEASES_API_URL, DIRECT_RELEASES_API_URL];
  }
}
