import { ServiceUnavailableException } from '@nestjs/common';
import { AiMusicPromptEnhancerService } from './ai-music-prompt-enhancer.service';
import { MediaService } from './media.service';

const createPcmWavBuffer = (frames = 8000) => {
  const sampleRate = 8000;
  const channels = 1;
  const bitsPerSample = 16;
  const bytesPerSample = bitsPerSample / 8;
  const dataSize = frames * channels * bytesPerSample;
  const buffer = Buffer.alloc(44 + dataSize);

  buffer.write('RIFF', 0, 'ascii');
  buffer.writeUInt32LE(36 + dataSize, 4);
  buffer.write('WAVE', 8, 'ascii');
  buffer.write('fmt ', 12, 'ascii');
  buffer.writeUInt32LE(16, 16);
  buffer.writeUInt16LE(1, 20);
  buffer.writeUInt16LE(channels, 22);
  buffer.writeUInt32LE(sampleRate, 24);
  buffer.writeUInt32LE(sampleRate * channels * bytesPerSample, 28);
  buffer.writeUInt16LE(channels * bytesPerSample, 32);
  buffer.writeUInt16LE(bitsPerSample, 34);
  buffer.write('data', 36, 'ascii');
  buffer.writeUInt32LE(dataSize, 40);

  for (let frame = 0; frame < frames; frame += 1) {
    const amplitude = frame < frames / 2 ? 0.2 : 0.9;
    const tone = Math.sin((frame / sampleRate) * Math.PI * 2 * 440);
    buffer.writeInt16LE(Math.round(tone * amplitude * 32767), 44 + frame * bytesPerSample);
  }

  return buffer;
};

describe('MediaService', () => {
  const createService = (
    googleCredentialsBase64 = '',
    overrides: {
      config?: Record<string, unknown>;
      storageService?: Record<string, unknown>;
      mediaProbeService?: Record<string, unknown>;
    } = {},
  ) => {
    const configService = {
      get: jest.fn((key: string) => {
        if (key === 'aiMusic.googleApplicationCredentialsJsonBase64') {
          return googleCredentialsBase64;
        }
        if (key in (overrides.config ?? {})) {
          return overrides.config?.[key];
        }
        return undefined;
      }),
    };

    return new MediaService(
      configService as any,
      (overrides.storageService ?? {}) as any,
      (overrides.mediaProbeService ?? {}) as any,
      new AiMusicPromptEnhancerService(),
    ) as any;
  };

  it('falls back to default Google application credentials when base64 JSON is missing', () => {
    const service = createService();

    expect(service.resolveGoogleApplicationCredentials()).toEqual({});
  });

  it('decodes service account credentials from base64 JSON', () => {
    const credentials = {
      type: 'service_account',
      client_email: 'ai-music@example.iam.gserviceaccount.com',
      private_key: '-----BEGIN PRIVATE KEY-----\nredacted\n-----END PRIVATE KEY-----\n',
    };
    const encoded = Buffer.from(JSON.stringify(credentials), 'utf8').toString('base64');
    const service = createService(encoded);

    expect(service.resolveGoogleApplicationCredentials()).toEqual({ credentials });
  });

  it('throws a safe configuration error for invalid base64 credentials', () => {
    const service = createService(Buffer.from('not-json', 'utf8').toString('base64'));

    expect(() => service.resolveGoogleApplicationCredentials()).toThrow(
      ServiceUnavailableException,
    );
    expect(() => service.resolveGoogleApplicationCredentials()).toThrow(
      'Invalid GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64',
    );
  });

  it('returns the probed audio duration instead of the requested duration', async () => {
    const audioBuffer = createPcmWavBuffer();
    const originalFetch = global.fetch;
    global.fetch = jest.fn().mockResolvedValue({
      ok: true,
      json: jest.fn().mockResolvedValue({
        predictions: [
          {
            bytesBase64Encoded: audioBuffer.toString('base64'),
            mimeType: 'audio/wav',
          },
        ],
      }),
    }) as any;
    const storageService = {
      saveFile: jest.fn().mockResolvedValue('/uploads/ai-music/generated.wav'),
      resolveLocalFilePath: jest.fn().mockReturnValue(null),
    };
    const mediaProbeService = {
      extractDurationSeconds: jest.fn(),
      extractDurationSecondsFromBuffer: jest.fn().mockResolvedValue(32),
    };
    const service = createService('', {
      config: {
        'aiMusic.enabled': true,
        'aiMusic.apiKey': 'test-api-key',
        'aiMusic.projectId': 'accordev',
        'aiMusic.location': 'us-central1',
        'aiMusic.model': 'lyria-002',
      },
      storageService,
      mediaProbeService,
    });

    try {
      const result = await service.generateMusicFromText('user-1', {
        prompt: 'test prompt',
        durationSeconds: 12,
      });

      expect(result.durationSeconds).toBe(32);
      expect(result.prompt).toBe('test prompt');
      expect(result.originalPrompt).toBe('test prompt');
      expect(result.enhancedPrompt).toContain('Instrumental only, no vocals');
      expect(result.detectedMusicIntent).toMatchObject({
        language: 'en',
        vocals: false,
      });
      expect(result.audioUrl).toBe('/uploads/ai-music/generated.wav');
      expect(result.mimeType).toBe('audio/wav');
      expect(result.sizeBytes).toBe(audioBuffer.length);
      expect(result.waveformPeaks).toHaveLength(100);
      expect(storageService.resolveLocalFilePath).toHaveBeenCalledWith(
        '/uploads/ai-music/generated.wav',
      );
      expect(mediaProbeService.extractDurationSeconds).not.toHaveBeenCalled();
      expect(mediaProbeService.extractDurationSecondsFromBuffer).toHaveBeenCalledWith(
        audioBuffer,
        {
          originalname: 'ai-music.wav',
          mimetype: 'audio/wav',
        },
      );
    } finally {
      global.fetch = originalFetch;
    }
  });

  it('sends the enhanced Arabic prompt to Lyria while preserving the original prompt', async () => {
    const audioBuffer = createPcmWavBuffer();
    const originalFetch = global.fetch;
    global.fetch = jest.fn().mockResolvedValue({
      ok: true,
      json: jest.fn().mockResolvedValue({
        predictions: [
          {
            bytesBase64Encoded: audioBuffer.toString('base64'),
            mimeType: 'audio/wav',
          },
        ],
      }),
    }) as any;
    const service = createService('', {
      config: {
        'aiMusic.enabled': true,
        'aiMusic.apiKey': 'test-api-key',
        'aiMusic.projectId': 'accordev',
        'aiMusic.location': 'us-central1',
        'aiMusic.model': 'lyria-002',
      },
      storageService: {
        saveFile: jest.fn().mockResolvedValue('/uploads/ai-music/generated.wav'),
        resolveLocalFilePath: jest.fn().mockReturnValue(null),
      },
      mediaProbeService: {
        extractDurationSeconds: jest.fn(),
        extractDurationSecondsFromBuffer: jest.fn().mockResolvedValue(12),
      },
    });

    try {
      const prompt = 'بدي لحن عود حزين مقام حجاز مع إيقاع خفيف';
      const result = await service.generateMusicFromText('user-1', {
        prompt,
        durationSeconds: 12,
      });
      const fetchBody = JSON.parse((global.fetch as jest.Mock).mock.calls[0][1].body);
      const sentPrompt = fetchBody.instances[0].prompt as string;

      expect(result.prompt).toBe(prompt);
      expect(result.originalPrompt).toBe(prompt);
      expect(result.enhancedPrompt).toBe(sentPrompt);
      expect(result.detectedMusicIntent).toMatchObject({
        language: 'ar',
        instrument: 'oud',
        maqam: 'hijaz',
        mood: 'sad',
        percussion: 'light',
      });
      expect(sentPrompt).toContain('Instrumental Arabic oud melody');
      expect(sentPrompt).toContain('Hijaz maqam, emotional Middle Eastern Arabic sound');
      expect(sentPrompt).toContain('sad, emotional, expressive');
      expect(sentPrompt).toContain('Light subtle percussion');
      expect(sentPrompt).toContain('slow tempo');
      expect(sentPrompt).toContain('Instrumental only, no vocals, no lyrics');
      expect(sentPrompt).toContain('No artist imitation, no copyrighted melody imitation');
    } finally {
      global.fetch = originalFetch;
    }
  });

  it('probes duration from the saved local audio file when local storage path is available', async () => {
    const audioBuffer = createPcmWavBuffer();
    const originalFetch = global.fetch;
    global.fetch = jest.fn().mockResolvedValue({
      ok: true,
      json: jest.fn().mockResolvedValue({
        predictions: [
          {
            bytesBase64Encoded: audioBuffer.toString('base64'),
            mimeType: 'audio/wav',
          },
        ],
      }),
    }) as any;
    const storageService = {
      saveFile: jest.fn().mockResolvedValue('/uploads/ai-music/generated.wav'),
      resolveLocalFilePath: jest
        .fn()
        .mockReturnValue('C:\\app\\uploads\\ai-music\\generated.wav'),
    };
    const mediaProbeService = {
      extractDurationSeconds: jest.fn().mockResolvedValue(32),
      extractDurationSecondsFromBuffer: jest.fn().mockResolvedValue(12),
    };
    const service = createService('', {
      config: {
        'aiMusic.enabled': true,
        'aiMusic.apiKey': 'test-api-key',
        'aiMusic.projectId': 'accordev',
        'aiMusic.location': 'us-central1',
        'aiMusic.model': 'lyria-002',
      },
      storageService,
      mediaProbeService,
    });

    try {
      const result = await service.generateMusicFromText('user-1', {
        prompt: 'test prompt',
        durationSeconds: 12,
      });

      expect(result.durationSeconds).toBe(32);
      expect(storageService.resolveLocalFilePath).toHaveBeenCalledWith(
        '/uploads/ai-music/generated.wav',
      );
      expect(mediaProbeService.extractDurationSeconds).toHaveBeenCalledWith(
        'C:\\app\\uploads\\ai-music\\generated.wav',
      );
      expect(mediaProbeService.extractDurationSecondsFromBuffer).not.toHaveBeenCalled();
    } finally {
      global.fetch = originalFetch;
    }
  });
});
