import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { call } from 'redux-saga/effects';
import { throwError } from 'redux-saga-test-plan/providers';
import * as Api from '../../../services/api';
import * as sagas from '../sagas';
import * as types from '../constants';
import { pollAssetStatus } from '../../../sagas/assetPolling';
import { ASSET_STATUS } from '../../../utils/assetStatusConstants';
import messages from '../messages';

describe('Viber Sagas', () => {
  describe('uploadViberAsset Saga', () => {
    const mockAsset = {
      _id: 'asset-123',
      type: 'IMAGE',
      url: 'https://example.com/image.jpg',
    };

    const mockParams = {
      file: new File(['test'], 'test.jpg', { type: 'image/jpeg' }),
      assetType: 'image',
      fileParams: {},
      templateType: 0,
    };

    it('handles async upload (202) with polling', () => {
      const mockResponse = {
        status: { code: 202 },
        response: {
          assetId: 'asset-123',
          asset: mockAsset,
          processingStatus: ASSET_STATUS.PROCESSING,
        },
      };

      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
          [matchers.call.fn(pollAssetStatus), undefined],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_PROCESSING,
          payload: {
            assetId: 'asset-123',
            asset: mockAsset,
            processingStatus: ASSET_STATUS.PROCESSING,
          },
        })
        .call.fn(Api.uploadFile)
        .call.fn(pollAssetStatus)
        .run();
    });

    it('handles async when processingStatus is processing with 200 status', () => {
      const mockResponse = {
        status: { code: 200 },
        response: {
          assetId: 'asset-123',
          asset: mockAsset,
          processingStatus: ASSET_STATUS.PROCESSING,
        },
      };

      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
          [matchers.call.fn(pollAssetStatus), undefined],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_PROCESSING,
          payload: {
            assetId: 'asset-123',
            asset: mockAsset,
            processingStatus: ASSET_STATUS.PROCESSING,
          },
        })
        .call.fn(pollAssetStatus)
        .run();
    });

    it('handles sync upload (201) without polling', () => {
      const mockResponse = {
        status: { code: 201 },
        response: {
          asset: mockAsset,
        },
      };

      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_SUCCESS,
          data: mockAsset,
          statusCode: 201,
          templateType: 0,
        })
        .not.call(pollAssetStatus)
        .run();
    });

    it('extracts assetId from asset._id when assetId omitted', () => {
      const mockResponse = {
        status: { code: 202 },
        response: {
          asset: { ...mockAsset, _id: 'asset-456' },
          processingStatus: ASSET_STATUS.PROCESSING,
        },
      };

      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
          [matchers.call.fn(pollAssetStatus), undefined],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_PROCESSING,
          payload: {
            assetId: 'asset-456',
            asset: { ...mockAsset, _id: 'asset-456' },
            processingStatus: ASSET_STATUS.PROCESSING,
          },
        })
        .run();
    });

    it('dispatches FAILED when assetId missing on async response', () => {
      const mockResponse = {
        status: { code: 202 },
        response: {
          processingStatus: ASSET_STATUS.PROCESSING,
        },
      };

      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_FAILED,
          payload: {
            assetId: undefined,
            error: messages.assetIdMissingError,
          },
          templateType: 0,
        })
        .not.call(pollAssetStatus)
        .run();
    });

    it('handles upload error', () => {
      const uploadError = new Error('Upload failed');

      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), throwError(uploadError)],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_FAILURE,
          error: uploadError,
        })
        .run();
    });

    it('falls back to statusCode on response when status object missing', () => {
      const mockResponse = {
        statusCode: 201,
        response: { asset: mockAsset },
      };
      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_SUCCESS,
          data: mockAsset,
          statusCode: 201,
          templateType: 0,
        })
        .run();
    });

    it('passes undefined statusCode through when no status info present (no default)', () => {
      const mockResponse = { response: { asset: mockAsset } };
      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_SUCCESS,
          data: mockAsset,
          statusCode: undefined,
          templateType: 0,
        })
        .run();
    });

    it('handles empty response object', () => {
      const mockResponse = { status: { code: 201 } };
      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_SUCCESS,
          data: {},
          statusCode: 201,
          templateType: 0,
        })
        .run();
    });

    it('normalizes a flat async response (asset fields at top level) for polling', () => {
      // API returns a flat asset object instead of { asset: {...} }
      const flatResponse = {
        status: { code: 202 },
        response: {
          _id: 'flat-asset-789',
          type: 'VIDEO',
          processingStatus: ASSET_STATUS.PROCESSING,
        },
      };
      return expectSaga(sagas.uploadViberAsset, { ...mockParams, assetType: undefined })
        .provide([
          [matchers.call.fn(Api.uploadFile), flatResponse],
          [matchers.call.fn(pollAssetStatus), undefined],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_PROCESSING,
          payload: {
            assetId: 'flat-asset-789',
            asset: flatResponse.response,
            processingStatus: ASSET_STATUS.PROCESSING,
          },
        })
        .call.fn(pollAssetStatus)
        .run();
    });

    it('normalizes a flat sync response (asset fields at top level)', () => {
      const flatAsset = { _id: 'flat-sync-1', url: 'https://cdn/x.jpg' };
      const flatResponse = {
        status: { code: 201 },
        response: flatAsset,
      };
      return expectSaga(sagas.uploadViberAsset, mockParams)
        .provide([
          [matchers.call.fn(Api.uploadFile), flatResponse],
        ])
        .put({
          type: types.UPLOAD_VIBER_ASSET_SUCCESS,
          data: flatAsset,
          statusCode: 201,
          templateType: 0,
        })
        .run();
    });

    it('derives assetType from asset.type when params.assetType omitted', () => {
      const paramsWithoutType = {
        file: new File(['v'], 'v.mp4', { type: 'video/mp4' }),
        fileParams: {},
        templateType: 1,
      };
      const mockResponse = {
        status: { code: 202 },
        response: {
          assetId: 'asset-vid',
          asset: { _id: 'asset-vid', type: 'VIDEO' },
        },
      };
      return expectSaga(sagas.uploadViberAsset, paramsWithoutType)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
          [matchers.call.fn(pollAssetStatus), undefined],
        ])
        .call.fn(pollAssetStatus)
        .run();
    });

    it('defaults assetType to image when no type info available', () => {
      const paramsWithoutType = {
        file: new File(['x'], 'x', { type: '' }),
        fileParams: {},
        templateType: 0,
      };
      const mockResponse = {
        status: { code: 202 },
        response: {
          assetId: 'asset-noop',
        },
      };
      return expectSaga(sagas.uploadViberAsset, paramsWithoutType)
        .provide([
          [matchers.call.fn(Api.uploadFile), mockResponse],
          [matchers.call.fn(pollAssetStatus), undefined],
        ])
        .call.fn(pollAssetStatus)
        .run();
    });
  });

  describe('createViberTemplate Saga', () => {
    const template = { content: 'New Viber Template' };
    const callback = jest.fn();

    it('handles creating template successfully', () => {
      const fakeResponse = {
        response: { id: 2, content: template.content },
        status: { code: 201 },
      };

      return expectSaga(sagas.createViberTemplate, { template, callback })
        .provide([
          [call(Api.createViberTemplate, { template }), fakeResponse],
        ])
        .put({
          type: types.CREATE_VIBER_TEMPLATE_SUCCESS,
          data: fakeResponse.response,
          statusCode: fakeResponse.status.code,
          errorMsg: undefined,
        })
        .run()
        .then(() => {
          expect(callback).toHaveBeenCalledWith(fakeResponse.response);
        });
    });

    it('handles failure in creating template', () => {
      const error = new Error({ message: 'Creation failed', status: { code: 400 } });

      return expectSaga(sagas.createViberTemplate, { template, callback })
        .provide([
          [call(Api.createViberTemplate, { template }), throwError(error)],
        ])
        .put({
          type: types.CREATE_VIBER_TEMPLATE_FAILURE,
          error,
          errorMsg: undefined,
        })
        .run()
        .then(() => {
          expect(callback).toHaveBeenCalledWith(null, undefined);
        });
    });

    it('handles 4xx API response (treated as failure)', () => {
      const cb = jest.fn();
      const fakeResponse = {
        response: {},
        status: { code: 400 },
        message: 'bad request',
      };
      return expectSaga(sagas.createViberTemplate, { template, callback: cb })
        .provide([
          [call(Api.createViberTemplate, { template }), fakeResponse],
        ])
        .put({
          type: types.CREATE_VIBER_TEMPLATE_FAILURE,
          error: 'bad request',
          errorMsg: 'bad request',
        })
        .run()
        .then(() => {
          expect(cb).toHaveBeenCalledWith(null, 'bad request');
        });
    });

    it('handles success when no callback is supplied', () => {
      const fakeResponse = {
        response: { id: 42 },
        status: { code: 201 },
      };
      return expectSaga(sagas.createViberTemplate, { template })
        .provide([
          [call(Api.createViberTemplate, { template }), fakeResponse],
        ])
        .put({
          type: types.CREATE_VIBER_TEMPLATE_SUCCESS,
          data: fakeResponse.response,
          statusCode: 201,
          errorMsg: undefined,
        })
        .run();
    });

    it('handles failure when no callback is supplied', () => {
      const error = new Error('Creation failed');
      return expectSaga(sagas.createViberTemplate, { template })
        .provide([
          [call(Api.createViberTemplate, { template }), throwError(error)],
        ])
        .put({
          type: types.CREATE_VIBER_TEMPLATE_FAILURE,
          error,
          errorMsg: undefined,
        })
        .run();
    });
  });

  describe('editTemplate Saga', () => {
    const template = { id: 1, content: 'Updated Content' };
    const callback = jest.fn();

    it('handles editing template successfully', () => {
      const fakeResponse = {
        response: { updated: true },
        status: { code: 200 },
      };

      return expectSaga(sagas.editTemplate, { template, callback })
        .provide([
          [call(Api.createViberTemplate, { template }), fakeResponse],
        ])
        .put({
          type: types.EDIT_VIBER_TEMPLATE_SUCCESS,
          data: fakeResponse.response,
          statusCode: fakeResponse.status.code,
          errorMsg: undefined,
        })
        .run()
        .then(() => {
          expect(callback).toHaveBeenCalledWith(fakeResponse.response);
        });
    });

    it('handles failure in editing template', () => {
      const errorMsg = 'Error in editing template';
      return expectSaga(sagas.editTemplate, { template, callback })
        .provide([
          [call(Api.createViberTemplate, { template }), throwError({ message: errorMsg, status: { code: 400 } })],
        ])
        .put({
          type: types.EDIT_VIBER_TEMPLATE_FAILURE,
          error: { message: 'Error in editing template', status: { code: 400 } },
          errorMsg: undefined,
        })
        .run()
        .then(() => {
          expect(callback).toHaveBeenCalledWith(null, undefined);
        });
    });

    it('handles 4xx API response (treated as failure)', () => {
      const cb = jest.fn();
      const fakeResponse = {
        response: {},
        status: { code: 422 },
        message: 'invalid payload',
      };
      return expectSaga(sagas.editTemplate, { template, callback: cb })
        .provide([
          [call(Api.createViberTemplate, { template }), fakeResponse],
        ])
        .put({
          type: types.EDIT_VIBER_TEMPLATE_FAILURE,
          error: 'invalid payload',
          errorMsg: 'invalid payload',
        })
        .run()
        .then(() => {
          expect(cb).toHaveBeenCalledWith(null, 'invalid payload');
        });
    });

    it('handles success when no callback is supplied', () => {
      const fakeResponse = {
        response: { updated: true },
        status: { code: 200 },
      };
      return expectSaga(sagas.editTemplate, { template })
        .provide([
          [call(Api.createViberTemplate, { template }), fakeResponse],
        ])
        .put({
          type: types.EDIT_VIBER_TEMPLATE_SUCCESS,
          data: fakeResponse.response,
          statusCode: 200,
          errorMsg: undefined,
        })
        .run();
    });

    it('handles failure when no callback is supplied', () => {
      const error = new Error('Edit failed');
      return expectSaga(sagas.editTemplate, { template })
        .provide([
          [call(Api.createViberTemplate, { template }), throwError(error)],
        ])
        .put({
          type: types.EDIT_VIBER_TEMPLATE_FAILURE,
          error,
          errorMsg: undefined,
        })
        .run();
    });
  });

  describe('getTemplateDetails Saga', () => {
    const id = '123';
    const callback = jest.fn();

    it('handles fetching template details successfully', () => {
      const fakeResponse = {
        response: { id, name: 'Detailed Template' },
      };

      return expectSaga(sagas.getTemplateDetails, { id, callback })
        .provide([
          [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), fakeResponse],
        ])
        .put({
          type: types.GET_VIBER_TEMPLATE_DETAILS_SUCCESS,
          data: fakeResponse.response,
        })
        .run()
        .then(() => {
          expect(callback).not.toHaveBeenCalledWith(0);
        });
    });

    it('handles failure in fetching template details', () => {
      const error = new Error('Fetch failed');

      return expectSaga(sagas.getTemplateDetails, { id, callback })
        .provide([
          [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), throwError(error)],
        ])
        .put({
          type: types.GET_VIBER_TEMPLATE_DETAILS_FAILURE,
          error,
        })
        .run()
        .then(() => {
          expect(callback).not.toHaveBeenCalledWith();
        });
    });

    it('handles success without a callback', () => {
      const fakeResponse = { response: { id, name: 'No CB' } };
      return expectSaga(sagas.getTemplateDetails, { id })
        .provide([
          [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), fakeResponse],
        ])
        .put({
          type: types.GET_VIBER_TEMPLATE_DETAILS_SUCCESS,
          data: fakeResponse.response,
        })
        .run();
    });
  });

  describe('v2ViberSagas Combined', () => {
    it('should initialize all Viber-related watcher sagas without error', () => expectSaga(sagas.v2ViberSagas).run());
  });
});
