// --- Mocks ---

const mockCheck = jest.fn();
const mockRequest = jest.fn();

jest.mock('react-native-permissions', () => ({
  check: (...args: unknown[]) => mockCheck(...args),
  request: (...args: unknown[]) => mockRequest(...args),
  PERMISSIONS: {
    IOS: {PHOTO_LIBRARY: 'ios.permission.PHOTO_LIBRARY'},
    ANDROID: {
      WRITE_EXTERNAL_STORAGE: 'android.permission.WRITE_EXTERNAL_STORAGE',
    },
  },
  RESULTS: {
    GRANTED: 'granted',
    DENIED: 'denied',
    BLOCKED: 'blocked',
    UNAVAILABLE: 'unavailable',
    LIMITED: 'limited',
  },
}));

const mockSaveAsset = jest.fn();
jest.mock('@react-native-camera-roll/camera-roll', () => ({
  CameraRoll: {saveAsset: (...args: unknown[]) => mockSaveAsset(...args)},
}));

const mockFileExists = jest.fn();
const mockFileDelete = jest.fn();
const mockDownloadFileAsync = jest.fn();

jest.mock('expo-file-system', () => {
  const MockFile = jest.fn().mockImplementation((uri: string) => ({
    uri,
    get exists() {
      return mockFileExists();
    },
    delete: mockFileDelete,
  }));
  (MockFile as any).downloadFileAsync = (...args: unknown[]) =>
    mockDownloadFileAsync(...args);

  // `downloadFromServer` does `dir.exists` + `dir.create(...)` if missing.
  // Default `exists: true` skips the create branch in the code under test.
  const MockDirectory = jest.fn().mockImplementation(() => ({
    exists: true,
    create: jest.fn(),
  }));

  return {
    Paths: {cache: '/cache'},
    File: MockFile,
    Directory: MockDirectory,
  };
});

const mockImageCompress = jest.fn();
jest.mock('react-native-compressor', () => ({
  Image: {compress: (...args: unknown[]) => mockImageCompress(...args)},
}));

const mockGetValidAccessToken = jest.fn();
jest.mock('@config/tokens', () => ({
  tokenManager: {getValidAccessToken: () => mockGetValidAccessToken()},
}));

const mockGetApiHeaders = jest.fn();
jest.mock('@config/api', () => ({
  getApiHeaders: (...args: unknown[]) => mockGetApiHeaders(...args),
}));

const mockGetAPILevel = jest.fn();
const mockSentryError = jest.fn();
const mockSentryWarn = jest.fn();
jest.mock('@shared/utils', () => ({
  isIos: false,
  getAPILevel: () => mockGetAPILevel(),
  sentryLogs: {
    error: (...args: unknown[]) => mockSentryError(...args),
    warn: (...args: unknown[]) => mockSentryWarn(...args),
  },
}));

// --- Helpers ---

let saveAssetToGallery: typeof import('../download').saveAssetToGallery;

function setupAndroid(apiLevel = 29) {
  mockGetAPILevel.mockReturnValue(apiLevel);
}

function fileExists(exists = true) {
  mockFileExists.mockReturnValue(exists);
}

// --- Tests ---

describe('chats download utils', () => {
  beforeEach(() => {
    jest.resetModules();
    jest.clearAllMocks();

    jest.isolateModules(() => {
      // eslint-disable-next-line @typescript-eslint/no-var-requires
      const mod = require('../download') as typeof import('../download');
      ({saveAssetToGallery} = mod);
    });
  });

  // ---- getFileExtension (tested indirectly via saveAssetToGallery) ----

  describe('getFileExtension (via photo save path)', () => {
    beforeEach(() => {
      setupAndroid(29); // Android 29+ auto-grants
      fileExists(true);
      mockSaveAsset.mockResolvedValue(undefined);
    });

    it('saves jpg directly without conversion', async () => {
      await saveAssetToGallery({
        uri: 'file://photo.jpg',
        remoteUrl: 'https://example.com/photo.jpg',
        type: 'photo',
      });

      expect(mockImageCompress).not.toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledWith('file://photo.jpg', {
        type: 'photo',
      });
    });

    it('saves jpeg directly without conversion', async () => {
      await saveAssetToGallery({
        uri: 'file://photo.jpeg',
        remoteUrl: 'https://example.com/photo.jpeg',
        type: 'photo',
      });

      expect(mockImageCompress).not.toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledWith('file://photo.jpeg', {
        type: 'photo',
      });
    });

    it('saves png directly without conversion', async () => {
      await saveAssetToGallery({
        uri: 'file://photo.png',
        remoteUrl: 'https://example.com/photo.png',
        type: 'photo',
      });

      expect(mockImageCompress).not.toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledWith('file://photo.png', {
        type: 'photo',
      });
    });

    it('converts webp to jpg before saving', async () => {
      mockImageCompress.mockResolvedValue('file://converted.jpg');

      await saveAssetToGallery({
        uri: 'file://photo.webp',
        remoteUrl: 'https://example.com/photo.webp',
        type: 'photo',
      });

      expect(mockImageCompress).toHaveBeenCalledWith('file://photo.webp', {
        output: 'jpg',
        compressionMethod: 'auto',
      });
      expect(mockSaveAsset).toHaveBeenCalledWith('file://converted.jpg', {
        type: 'photo',
      });
    });

    it('converts heic to jpg before saving', async () => {
      mockImageCompress.mockResolvedValue('file://converted.jpg');

      await saveAssetToGallery({
        uri: 'file://photo.heic',
        remoteUrl: 'https://example.com/photo.heic',
        type: 'photo',
      });

      expect(mockImageCompress).toHaveBeenCalledWith('file://photo.heic', {
        output: 'jpg',
        compressionMethod: 'auto',
      });
      expect(mockSaveAsset).toHaveBeenCalledWith('file://converted.jpg', {
        type: 'photo',
      });
    });

    it('handles url with query params when extracting extension', async () => {
      await saveAssetToGallery({
        uri: 'file://photo.png?token=abc&size=large',
        remoteUrl: 'https://example.com/photo.png',
        type: 'photo',
      });

      expect(mockImageCompress).not.toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledWith(
        'file://photo.png?token=abc&size=large',
        {type: 'photo'},
      );
    });

    it('handles url with hash fragment when extracting extension', async () => {
      await saveAssetToGallery({
        uri: 'file://photo.jpg#anchor',
        remoteUrl: 'https://example.com/photo.jpg',
        type: 'photo',
      });

      expect(mockImageCompress).not.toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledWith('file://photo.jpg#anchor', {
        type: 'photo',
      });
    });
  });

  // ---- downloadFromServer fallback ----

  describe('server fallback via downloadFromServer', () => {
    beforeEach(() => {
      setupAndroid(29);
    });

    it('downloads from server when uri is null', async () => {
      const fakeRemoteFile = {
        uri: 'file://cached/remote.jpg',
        delete: mockFileDelete,
      };
      mockDownloadFileAsync.mockResolvedValue(fakeRemoteFile);
      mockGetValidAccessToken.mockResolvedValue('tok-123');
      mockGetApiHeaders.mockReturnValue({Authorization: 'Bearer tok-123'});
      mockSaveAsset.mockResolvedValue(undefined);

      await saveAssetToGallery({
        uri: null,
        remoteUrl: 'https://example.com/photo.jpg',
        type: 'photo',
      });

      expect(mockDownloadFileAsync).toHaveBeenCalledWith(
        'https://example.com/photo.jpg',
        expect.anything(),
        expect.objectContaining({headers: {Authorization: 'Bearer tok-123'}}),
      );
      expect(mockSaveAsset).toHaveBeenCalledWith('file://cached/remote.jpg', {
        type: 'photo',
      });
      expect(mockFileDelete).toHaveBeenCalled();
    });

    it('downloads from server when local file does not exist', async () => {
      fileExists(false);
      const fakeRemoteFile = {
        uri: 'file://cached/remote.mp4',
        delete: mockFileDelete,
      };
      mockDownloadFileAsync.mockResolvedValue(fakeRemoteFile);
      mockGetValidAccessToken.mockResolvedValue('tok-456');
      mockGetApiHeaders.mockReturnValue({Authorization: 'Bearer tok-456'});
      mockSaveAsset.mockResolvedValue(undefined);

      await saveAssetToGallery({
        uri: 'file://local/video.mp4',
        remoteUrl: 'https://example.com/video.mp4',
        type: 'video',
      });

      expect(mockDownloadFileAsync).toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledWith('file://cached/remote.mp4', {
        type: 'video',
      });
      expect(mockFileDelete).toHaveBeenCalled();
    });

    it('falls back to server when local save fails', async () => {
      fileExists(true);
      mockSaveAsset
        .mockRejectedValueOnce(new Error('Local save failed'))
        .mockResolvedValueOnce(undefined);

      const fakeRemoteFile = {
        uri: 'file://cached/remote.jpg',
        delete: mockFileDelete,
      };
      mockDownloadFileAsync.mockResolvedValue(fakeRemoteFile);
      mockGetValidAccessToken.mockResolvedValue('tok-789');
      mockGetApiHeaders.mockReturnValue({});

      await saveAssetToGallery({
        uri: 'file://photo.jpg',
        remoteUrl: 'https://example.com/photo.jpg',
        type: 'photo',
      });

      expect(mockDownloadFileAsync).toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledTimes(2);
      expect(mockFileDelete).toHaveBeenCalled();
    });

    it('cleans up the temp file even after a successful download', async () => {
      const fakeRemoteFile = {
        uri: 'file://cached/remote.jpg',
        delete: mockFileDelete,
      };
      mockDownloadFileAsync.mockResolvedValue(fakeRemoteFile);
      mockGetValidAccessToken.mockResolvedValue('tok');
      mockGetApiHeaders.mockReturnValue({});
      mockSaveAsset.mockResolvedValue(undefined);

      await saveAssetToGallery({
        uri: null,
        remoteUrl: 'https://example.com/photo.jpg',
        type: 'photo',
      });

      expect(mockFileDelete).toHaveBeenCalledTimes(1);
    });
  });

  // ---- saveAssetToGallery — video path ----

  describe('saveAssetToGallery — video', () => {
    it('saves video without conversion', async () => {
      setupAndroid(29);
      fileExists(true);
      mockSaveAsset.mockResolvedValue(undefined);

      await saveAssetToGallery({
        uri: 'file://video.mp4',
        remoteUrl: 'https://example.com/video.mp4',
        type: 'video',
      });

      expect(mockImageCompress).not.toHaveBeenCalled();
      expect(mockSaveAsset).toHaveBeenCalledWith('file://video.mp4', {
        type: 'video',
      });
    });
  });
});
