import axios from 'axios';

// Common trackers to include in magnet URIs so RD/AD can find the torrent
const TRACKERS = [
  'udp://open.demonii.com:1337/announce',
  'udp://tracker.openbittorrent.com:80',
  'udp://tracker.coppersurfer.tk:6969',
  'udp://glotorrents.pw:6969/announce',
  'udp://tracker.opentrackr.org:1337/announce',
  'udp://torrent.gresille.org:80/announce',
  'udp://p4p.arenabg.com:1337',
  'udp://tracker.leechers-paradise.org:6969',
].map(t => `&tr=${encodeURIComponent(t)}`).join('');

function buildMagnet(infoHash: string): string {
  return `magnet:?xt=urn:btih:${infoHash.toLowerCase()}${TRACKERS}`;
}

/** Encode an object as application/x-www-form-urlencoded */
function formEncode(data: Record<string, string>): string {
  return Object.entries(data)
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join('&');
}

// ─── Real-Debrid ──────────────────────────────────────────────────────────────

export class RealDebridService {
  private apiKey: string;
  private baseUrl = 'https://api.real-debrid.com/rest/1.0';

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  private get headers() {
    return {Authorization: `Bearer ${this.apiKey}`};
  }

  private get formHeaders() {
    return {
      Authorization: `Bearer ${this.apiKey}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    };
  }

  async checkCache(infoHash: string): Promise<boolean> {
    try {
      const hash = infoHash.toLowerCase();
      const res = await axios.get(
        `${this.baseUrl}/torrents/instantAvailability/${hash}`,
        {headers: this.headers, timeout: 8000},
      );
      const data = res.data as Record<string, unknown>;
      const hashData = data[hash];
      if (!hashData) return false;
      const hosters = (hashData as Record<string, unknown>).rd;
      return Array.isArray(hosters) && hosters.length > 0;
    } catch {
      return false;
    }
  }

  async resolveStream(infoHash: string, fileIdx?: number): Promise<string> {
    const magnet = buildMagnet(infoHash);

    // 1. Add magnet
    const addRes = await axios.post(
      `${this.baseUrl}/torrents/addMagnet`,
      formEncode({magnet}),
      {headers: this.formHeaders, timeout: 15000},
    );
    const torrentId: string = (addRes.data as {id: string}).id;
    if (!torrentId) throw new Error('Real-Debrid: no torrent ID returned');

    // 2. Select files (select all, then pick by index later)
    await axios.post(
      `${this.baseUrl}/torrents/selectFiles/${torrentId}`,
      formEncode({files: 'all'}),
      {headers: this.formHeaders, timeout: 10000},
    );

    // 3. Poll until downloaded
    let links: string[] = [];
    for (let attempt = 0; attempt < 20; attempt++) {
      await sleep(3000);
      const infoRes = await axios.get(
        `${this.baseUrl}/torrents/info/${torrentId}`,
        {headers: this.headers, timeout: 10000},
      );
      const info = infoRes.data as {status: string; links: string[]};
      if (info.status === 'downloaded' && info.links?.length > 0) {
        links = info.links;
        break;
      }
      // If it errored out, stop waiting
      if (['error', 'dead', 'magnet_error'].includes(info.status)) {
        throw new Error(`Real-Debrid torrent status: ${info.status}`);
      }
    }

    if (links.length === 0) {
      throw new Error('Real-Debrid: timed out waiting for download');
    }

    // 4. Pick the right file link by index
    const linkIdx =
      fileIdx !== undefined ? Math.min(fileIdx, links.length - 1) : 0;
    const link = links[linkIdx];

    // 5. Unrestrict the hosted link → direct download URL
    const unrestrictRes = await axios.post(
      `${this.baseUrl}/unrestrict/link`,
      formEncode({link}),
      {headers: this.formHeaders, timeout: 10000},
    );
    const download = (unrestrictRes.data as {download: string}).download;
    if (!download) throw new Error('Real-Debrid: no download URL returned');
    return download;
  }

  async getAccountInfo(): Promise<{
    username: string;
    premium: boolean;
    expiration: string;
  }> {
    const res = await axios.get(`${this.baseUrl}/user`, {
      headers: this.headers,
      timeout: 10000,
    });
    const data = res.data as {
      username: string;
      type: string;
      expiration: string;
    };
    return {
      username: data.username,
      premium: data.type === 'premium',
      expiration: data.expiration,
    };
  }
}

// ─── AllDebrid ────────────────────────────────────────────────────────────────

export class AllDebridService {
  private apiKey: string;
  private baseUrl = 'https://api.alldebrid.com/v4';
  private agent = 'StreamVault';

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  async checkCache(infoHash: string): Promise<boolean> {
    try {
      const res = await axios.get(`${this.baseUrl}/magnet/instant`, {
        params: {
          agent: this.agent,
          apikey: this.apiKey,
          magnets: infoHash.toLowerCase(),
        },
        timeout: 8000,
      });
      const data = res.data as {
        data?: {magnets?: Array<{instant: boolean}>};
      };
      return data.data?.magnets?.[0]?.instant === true;
    } catch {
      return false;
    }
  }

  async resolveStream(infoHash: string, fileIdx?: number): Promise<string> {
    const magnet = buildMagnet(infoHash);

    // 1. Upload magnet
    const uploadRes = await axios.get(`${this.baseUrl}/magnet/upload`, {
      params: {agent: this.agent, apikey: this.apiKey, magnets: magnet},
      timeout: 15000,
    });
    const magnets = (
      uploadRes.data as {data?: {magnets?: Array<{id: number; error?: string}>}}
    ).data?.magnets;
    if (!magnets?.length) throw new Error('AllDebrid: upload failed');
    if (magnets[0].error) throw new Error(`AllDebrid: ${magnets[0].error}`);
    const magnetId = magnets[0].id;

    // 2. Poll until ready
    let links: Array<{link: string}> = [];
    for (let attempt = 0; attempt < 20; attempt++) {
      await sleep(3000);
      const statusRes = await axios.get(`${this.baseUrl}/magnet/status`, {
        params: {agent: this.agent, apikey: this.apiKey, id: magnetId},
        timeout: 10000,
      });
      const status = (
        statusRes.data as {
          data?: {
            magnets?: {statusCode: number; links?: Array<{link: string}>};
          };
        }
      ).data?.magnets;

      if (status?.statusCode === 4 && status.links?.length) {
        links = status.links;
        break;
      }
      if (status?.statusCode && status.statusCode > 4) {
        throw new Error(`AllDebrid torrent status code: ${status.statusCode}`);
      }
    }

    if (links.length === 0) {
      throw new Error('AllDebrid: timed out waiting for download');
    }

    // 3. Unlock the link
    const linkIdx =
      fileIdx !== undefined ? Math.min(fileIdx, links.length - 1) : 0;
    const link = links[linkIdx].link;

    const unlockRes = await axios.get(`${this.baseUrl}/link/unlock`, {
      params: {agent: this.agent, apikey: this.apiKey, link},
      timeout: 10000,
    });
    const resolved = (unlockRes.data as {data?: {link: string}}).data?.link;
    if (!resolved) throw new Error('AllDebrid: no download URL returned');
    return resolved;
  }
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

export type DebridProvider = 'realdebrid' | 'alldebrid' | 'none';

export interface DebridConfig {
  provider: DebridProvider;
  apiKey: string;
}

export function createDebridService(
  config: DebridConfig,
): RealDebridService | AllDebridService | null {
  if (config.provider === 'realdebrid' && config.apiKey)
    return new RealDebridService(config.apiKey);
  if (config.provider === 'alldebrid' && config.apiKey)
    return new AllDebridService(config.apiKey);
  return null;
}
