import { BadGatewayException, BadRequestException, Injectable } from '@nestjs/common';
import { posix } from 'path';
import { AppLoggerService } from '../../infrastructure/logging/app-logger.service';
import { MediaStorageService } from './media-storage.service';

const PLAYLIST_EXTENSION = '.m3u8';
const SIGNED_ASSET_EXTENSIONS = new Set(['.m4s', '.ts', '.mp4', '.aac', '.vtt']);
const URI_ATTRIBUTE_PATTERN = /URI=(["'])(.*?)\1/g;

@Injectable()
export class HlsPlaylistService {
  constructor(
    private readonly mediaStorageService: MediaStorageService,
    private readonly appLogger: AppLoggerService,
  ) {}

  async rewritePlaylist(
    requestedPath: string,
    access?: { expires?: string; signature?: string },
  ): Promise<string> {
    if (!this.mediaStorageService.shouldSignResponseUrls()) {
      throw new BadRequestException('Private HLS rewrite is disabled');
    }

    const key = this.normalizePlaylistKey(requestedPath);
    if (!this.mediaStorageService.isValidHlsPlaylistAccess(key, access?.expires, access?.signature)) {
      throw new BadRequestException('Invalid or expired HLS access signature');
    }
    const bucket = this.mediaStorageService.getBucketName();
    const startedAt = Date.now();
    this.appLogger.log(
      {
        event: 'hls_playlist_rewrite_started',
        key,
        bucket,
      },
      HlsPlaylistService.name,
    );

    try {
      const playlist = await this.mediaStorageService.getObjectText(key);
      const rewrittenLines = await Promise.all(
        playlist.split(/\r?\n/).map((line) => this.rewriteLine(line, key)),
      );
      const rewritten = rewrittenLines.join('\n');

      this.appLogger.log(
        {
          event: 'hls_playlist_rewrite_completed',
          key,
          bucket,
          durationMs: Date.now() - startedAt,
        },
        HlsPlaylistService.name,
      );
      return rewritten;
    } catch (error) {
      const errorName = error instanceof Error ? error.name : 'UnknownError';
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.appLogger.error(
        {
          event: 'hls_playlist_rewrite_failed',
          key,
          bucket,
          durationMs: Date.now() - startedAt,
          errorName,
          errorMessage,
        },
        error instanceof Error ? error.stack : undefined,
        HlsPlaylistService.name,
      );
      throw new BadGatewayException('Unable to load HLS playlist');
    }
  }

  private async rewriteLine(line: string, playlistKey: string): Promise<string> {
    const trimmed = line.trim();
    if (!trimmed) {
      return line;
    }

    if (!trimmed.startsWith('#')) {
      return this.rewriteUri(trimmed, playlistKey);
    }

    const matches = Array.from(line.matchAll(URI_ATTRIBUTE_PATTERN));
    if (!matches.length) {
      return line;
    }

    let rewritten = line;
    for (const match of matches) {
      const originalUri = match[2];
      const rewrittenUri = await this.rewriteUri(originalUri, playlistKey);
      rewritten = rewritten.replace(match[0], `URI=${match[1]}${rewrittenUri}${match[1]}`);
    }
    return rewritten;
  }

  private async rewriteUri(uri: string, playlistKey: string): Promise<string> {
    const targetKey = this.resolveTargetKey(uri, playlistKey);
    if (!targetKey) {
      return uri;
    }

    const extension = posix.extname(targetKey).toLowerCase();
    if (extension === PLAYLIST_EXTENSION) {
      return this.mediaStorageService.getHlsPlaylistUrl(targetKey);
    }
    if (!SIGNED_ASSET_EXTENSIONS.has(extension)) {
      return uri;
    }

    const signedUrl = await this.mediaStorageService.getSignedObjectUrl(targetKey);
    if (!signedUrl) {
      throw new Error(`Failed to sign HLS asset "${targetKey}"`);
    }
    return signedUrl;
  }

  private resolveTargetKey(uri: string, playlistKey: string): string | null {
    const cleanUri = uri.split('#')[0].split('?')[0].trim();
    if (!cleanUri) {
      return null;
    }

    if (/^[a-z][a-z0-9+.-]*:\/\//i.test(cleanUri) || cleanUri.startsWith('/')) {
      const resolvedKey = this.mediaStorageService.resolveKeyFromUrl(cleanUri);
      return resolvedKey && this.mediaStorageService.isManagedKey(resolvedKey) ? resolvedKey : null;
    }

    let decodedUri = cleanUri;
    try {
      decodedUri = decodeURIComponent(cleanUri);
    } catch {
      decodedUri = cleanUri;
    }

    const targetKey = posix.normalize(posix.join(posix.dirname(playlistKey), decodedUri));
    return targetKey.startsWith('../') ||
      targetKey === '..' ||
      !this.mediaStorageService.isManagedKey(targetKey)
      ? null
      : targetKey;
  }

  private normalizePlaylistKey(requestedPath: string): string {
    let decodedPath = requestedPath;
    try {
      decodedPath = decodeURIComponent(requestedPath);
    } catch {
      decodedPath = requestedPath;
    }

    const normalized = decodedPath.replace(/\\/g, '/').replace(/^\/+/, '');
    if (
      !normalized ||
      !normalized.toLowerCase().endsWith(PLAYLIST_EXTENSION) ||
      normalized.split('/').some((segment) => !segment || segment === '.' || segment === '..') ||
      !this.mediaStorageService.isManagedKey(normalized)
    ) {
      throw new BadRequestException('Invalid HLS playlist path');
    }
    return normalized;
  }
}
