import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { call, takeLatest, put } from 'redux-saga/effects';
import * as api from '../../../services/api';
import * as types from '../constants';
import * as cdnUtils from '../../../utils/cdnTransformation';
import {
  getCdnTransformationConfig,
  getAllTemplates,
  watchGetCdnTransformationConfig,
  watchGetAllTemplates,
  getSenderDetails,
  fetchWeCrmAccounts,
  sendZippedFile,
  fetchUserList,
  watchGetUserList,
  deleteRcsTemplate,
  watchDeleteRcsTemplate,
  watchForGetTemplateInfoById,
  deleteTemplate,
  watchDeleteTemplate,
  getTemplateDetails,
  watchGetTemplateDetails,
  getDefaultBeeTemplates,
  watchGetDefaultBeeTemplates,
  getOrgLevelCampaignSettings,
  watchGetOrgLevelCampaignSettings,
  watchSendingFile,
  watchFetchWeCrmAccounts,
  watchGetSenderDetails,
  v2TemplateSaga,
  v2TemplateSagaWatchGetDefaultBeeTemplates,
  archiveTemplateSaga,
  unarchiveTemplateSaga,
  bulkArchiveTemplatesSaga,
  bulkUnarchiveTemplatesSaga,
  watchArchiveTemplate,
  watchUnarchiveTemplate,
  watchBulkArchive,
  watchBulkUnarchive,
} from '../sagas';
import { getTemplateInfoById } from '../../Zalo/saga';
import { ZALO_TEMPLATE_INFO_REQUEST } from '../../Zalo/constants';
import * as mockData from './mockData';

jest.mock('@capillarytech/cap-ui-library', () => ({
  CapNotification: {
    success: jest.fn(),
    error: jest.fn(),
    warning: jest.fn(),
    info: jest.fn(),
  },
}));

describe('getCdnTransformationConfig saga', () => {
  afterEach(() => {
    delete window.APP_ENV;
  });

  it("short-circuits to env config and skips the API call when window.APP_ENV is set", async () => {
    const initSpy = jest.spyOn(cdnUtils, "initCdnConfigFromEnv").mockReturnValue(true);
    const apiSpy = jest.spyOn(api, "getCdnTransformationConfig");

    await expectSaga(getCdnTransformationConfig).run();

    expect(initSpy).toHaveBeenCalled();
    expect(apiSpy).not.toHaveBeenCalled();
  });

  it("handle valid response from api", async () => {
    jest.spyOn(cdnUtils, "initCdnConfigFromEnv").mockReturnValue(false);
    const saveCdnConfigsSpy = jest.spyOn(cdnUtils, "saveCdnConfigs");

    await expectSaga(getCdnTransformationConfig)
      .provide([
        [
          call(api.getCdnTransformationConfig),
          mockData.getCdnTransformationConfigSuccess,
        ],
      ])
      .run();
    expect(saveCdnConfigsSpy).toHaveBeenCalled();
  });

    it("handle error response from api", async () => {
      jest.spyOn(cdnUtils, "initCdnConfigFromEnv").mockReturnValue(false);
      const removeAllCdnLocalStorageItemsSpy = jest.spyOn(
        cdnUtils,
        "removeAllCdnLocalStorageItems"
      );

      await expectSaga(getCdnTransformationConfig)
        .provide([
          [
            call(api.getCdnTransformationConfig),
            mockData.getCdnTransformationConfigFailure,
          ],
        ])
        .run();
      expect(removeAllCdnLocalStorageItemsSpy).toHaveBeenCalled();
    });

    it("remove localStorage items when an error is thrown", async () => {
        jest.spyOn(cdnUtils, "initCdnConfigFromEnv").mockReturnValue(false);
        const saveCdnConfigsSpy = jest.spyOn(cdnUtils, "saveCdnConfigs").mockImplementation(()=>{
            throw new Error()
        });
        const removeAllCdnLocalStorageItemsSpy = jest.spyOn(
            cdnUtils,
            "removeAllCdnLocalStorageItems"
          );

        await expectSaga(getCdnTransformationConfig)
          .provide([
            [
              call(api.getCdnTransformationConfig),
              mockData.getCdnTransformationConfigSuccess,
            ],
          ])
          .run();
        expect(removeAllCdnLocalStorageItemsSpy).toHaveBeenCalled();
      });
});


describe('watchForGetJourneyList saga', () => {
    const generator = watchGetCdnTransformationConfig();
    it('should call watchers functions', () => {
      expect(generator.next().value).toEqual(
        takeLatest(types.GET_CDN_TRANSFORMATION_CONFIG_REQUEST, getCdnTransformationConfig),
      );
    });
});

describe('getOrgLevelCampaignSettings saga', () => {
  it('handles success', () => {
    const apiResult = { ok: true };
    const generator = getOrgLevelCampaignSettings();
    expect(generator.next().value).toEqual(call(api.getOrgLevelCampaignSettings));
    expect(generator.next(apiResult).value).toEqual(put({
      type: types.GET_ORG_LEVEL_CAMPAIGN_SETTINGS_SUCCESS,
      result: apiResult,
    }));
    expect(generator.next().done).toBe(true);
  });

  it('handles failure', () => {
    const error = new Error('oops');
    const generator = getOrgLevelCampaignSettings();
    expect(generator.next().value).toEqual(call(api.getOrgLevelCampaignSettings));
    expect(generator.throw(error).value).toEqual(put({
      type: types.GET_ORG_LEVEL_CAMPAIGN_SETTINGS_FAILURE,
      error,
    }));
    expect(generator.next().done).toBe(true);
  });
});

describe('templateList saga', () => {
  it('handle valid response from api', () => {
    expectSaga(getAllTemplates, mockData.getAllTemplatesListSuccess)
    .provide([
      [
        matchers.call.fn(api.getAllTemplates),
        mockData.getAllTemplatesListSuccess,
      ],
    ]).put({
        type: types.GET_ALL_TEMPLATES_SUCCESS,
        data: mockData.getAllTemplatesListSuccess,
        weCRMTemplate: mockData.getAllTemplatesListSuccess,
        isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
      })
      .run();
  });

  it('handle valid response from api', () => {
    mockData.getAllTemplatesListSuccess.intlCopyOf = '';
    expectSaga(getAllTemplates, mockData.getAllTemplatesListSuccess)
    .provide([
      [
        matchers.call.fn(api.getAllTemplates),
        mockData.getAllTemplatesListSuccess,
      ],
    ]).put({
        type: types.GET_ALL_TEMPLATES_SUCCESS,
        data: mockData.getAllTemplatesListSuccess,
        weCRMTemplate: mockData.getAllTemplatesListSuccess,
        isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
      })
      .run();
  });
  it('handles error thrown from api', () => {
    const failurePayload = {
      channel: { channel: 'RCS', queryParams: { page: 1 } },
      queryParams: { page: 1 },
    };
    const error = new Error('fail');
    expectSaga(getAllTemplates, failurePayload)
      .provide([[matchers.call.fn(api.getAllTemplates), throwError(error)]])
      .put({
        type: types.GET_ALL_TEMPLATES_FAILURE,
        error,
      })
      .run();
  });
});

describe('watchForTemplates saga', () => {
  const generator = watchGetAllTemplates();
  it('should call watchers functions', () => {
    expect(generator.next().value).toEqual(
      takeLatest(types.GET_ALL_TEMPLATES_REQUEST, getAllTemplates),
    );
  });
});

describe('getDefaultBeeTemplates saga', () => {
  it('handles success', () => {
    const apiResult = { response: [{ id: 1 }] };
    const generator = getDefaultBeeTemplates();
    expect(generator.next().value).toEqual(call(api.getDefaultBeeTemplates));
    expect(generator.next(apiResult).value).toEqual(put({
      type: types.GET_DEAFULT_BEE_TEMPLATES_SUCCESS,
      data: apiResult.response,
    }));
    expect(generator.next().done).toBe(true);
  });

  it('handles failure', () => {
    const error = new Error('bee');
    const generator = getDefaultBeeTemplates();
    expect(generator.next().value).toEqual(call(api.getDefaultBeeTemplates));
    expect(generator.throw(error).value).toEqual(put({
      type: types.GET_DEAFULT_BEE_TEMPLATES_FAILURE,
      error,
    }));
    expect(generator.next().done).toBe(true);
  });
});

describe('watchGetDefaultBeeTemplates saga', () => {
  const generator = watchGetDefaultBeeTemplates();
  it('should call watchers functions', () => {
    expect(generator.next().value).toEqual(
      takeLatest(types.GET_DEAFULT_BEE_TEMPLATES_REQUEST, getDefaultBeeTemplates),
    );
  });
});

describe('getSenderDetails Saga', () => {
  const channel = 'someChannel';
  const orgUnitId = 'someOrgUnitId';
  const action = { channel, orgUnitId };

  it('should handle apiResponse with hostName', () => {
    const apiResponse = {
      entity: {
        [channel]: [
          {
            domainProperties: {
              hostName: 'example.com',
            },
          },
        ],
      },
    };

    const generator = getSenderDetails(action);
    expect(generator.next().value).toEqual(call(api.getSenderDetails, channel, orgUnitId));
    expect(generator.next(apiResponse).value).toEqual(
      put({
        type: types.GET_SENDER_DETAILS_SUCCESS,
        payload: 'example.com',
      })
    );
  });

  it('should handle apiResponse without hostName', () => {
    const apiResponse = {
      entity: {
        [channel]: [
          {
            domainProperties: {},
          },
        ],
      },
    };

    const generator = getSenderDetails(action);
    expect(generator.next().value).toEqual(call(api.getSenderDetails, channel, orgUnitId));
    expect(generator.next(apiResponse).value).toEqual(
      put({
        type: types.GET_SENDER_DETAILS_SUCCESS,
        payload: '',
      })
    );
  });

  it('should handle WHATSAPP channel with domainProperties format', () => {
    const whatsappChannel = 'WHATSAPP';
    const whatsappOrgUnitId = 'org123';
    const whatsappAction = { channel: whatsappChannel, orgUnitId: whatsappOrgUnitId };
    const apiResponse = {
      entity: {
        WHATSAPP: { accountId: 'wa-123', phoneNumber: '+1234567890' },
      },
    };

    const generator = getSenderDetails(whatsappAction);
    expect(generator.next().value).toEqual(call(api.getSenderDetails, whatsappChannel, whatsappOrgUnitId));
    expect(generator.next(apiResponse).value).toEqual(
      put({
        type: types.GET_SENDER_DETAILS_SUCCESS,
        payload: {
          channel: whatsappChannel,
          domainProperties: apiResponse.entity.WHATSAPP,
        },
      })
    );
  });

  it('should handle ZALO channel with domainProperties format', () => {
    const zaloChannel = 'ZALO';
    const zaloOrgUnitId = 'org456';
    const zaloAction = { channel: zaloChannel, orgUnitId: zaloOrgUnitId };
    const apiResponse = {
      entity: {
        ZALO: { oaId: 'zalo-oa-123', token: 'zalo-token' },
      },
    };

    const generator = getSenderDetails(zaloAction);
    expect(generator.next().value).toEqual(call(api.getSenderDetails, zaloChannel, zaloOrgUnitId));
    expect(generator.next(apiResponse).value).toEqual(
      put({
        type: types.GET_SENDER_DETAILS_SUCCESS,
        payload: {
          channel: zaloChannel,
          domainProperties: apiResponse.entity.ZALO,
        },
      })
    );
  });

  it('should handle RCS channel with domainProperties format', () => {
    const rcsChannel = 'RCS';
    const rcsOrgUnitId = 'org789';
    const rcsAction = { channel: rcsChannel, orgUnitId: rcsOrgUnitId };
    const apiResponse = {
      entity: {
        RCS: { accountId: 'rcs-account-123', hostName: 'rcs.example.com' },
      },
    };

    const generator = getSenderDetails(rcsAction);
    expect(generator.next().value).toEqual(call(api.getSenderDetails, rcsChannel, rcsOrgUnitId));
    expect(generator.next(apiResponse).value).toEqual(
      put({
        type: types.GET_SENDER_DETAILS_SUCCESS,
        payload: {
          channel: rcsChannel,
          domainProperties: apiResponse.entity.RCS,
        },
      })
    );
  });

  it('should handle apiResponse with errors', () => {
    const apiResponse = {
      errors: ['Error 1', 'Error 2'],
    };

    const generator = getSenderDetails(action);
    expect(generator.next().value).toEqual(call(api.getSenderDetails, channel, orgUnitId));
    expect(generator.next(apiResponse).value).toEqual(
      put({
        type: types.GET_SENDER_DETAILS_FAILURE,
        payload: apiResponse.errors,
      })
    );
  });

  it('should handle exception thrown during API call', () => {
    const error = new Error('Network error');

    const generator = getSenderDetails(action);
    expect(generator.next().value).toEqual(call(api.getSenderDetails, channel, orgUnitId));
    expect(generator.throw(error).value).toEqual(
      put({
        type: types.GET_SENDER_DETAILS_FAILURE,
        payload: [],
      })
    );
  });
});

describe('fetchWeCrmAccounts Saga', () => {
  const action = { source: 'CRM' };
  test.concurrent('handles successful fetching of WeCRM accounts', () => {
    const fakeResponse = {
      response: [{ id: 1, name: 'Account One' }]
    };
    const generator = fetchWeCrmAccounts(action);
    expect(generator.next().value).toEqual(call(api.fetchWeCrmAccounts, action.source));
    expect(generator.next(fakeResponse).value).toEqual(
      put({
        type: types.GET_WECRM_ACCOUNTS_SUCCESS,
        data: fakeResponse.response
      })
    );
  });
  test.concurrent('handles error thrown from api', () => {
    const error = new Error('Fetch failed');
    expectSaga(fetchWeCrmAccounts, action.source)
      .provide([
        [
          call(api.fetchWeCrmAccounts),
          throwError(error)
        ],
      ])
      .put({
        type: types.GET_WECRM_ACCOUNTS_FAILURE,
        data: error
      })
      .run();
  });
});


describe('sendZippedFile Saga', () => {
  const action = {
    selectedFile: 'dummyFile.zip',
    errorHandler: jest.fn(),
    successHandler: jest.fn()
  };

  it('handles successful sending of zipped file', () => {
    const fakeResponse = {
      status: { isError: false },
      response: { metaEntity: { htmlContent: encodeURIComponent('<html></html>') } }
    };
    const generator = sendZippedFile(action);
    expect(generator.next().value).toEqual(call(api.sendZippedFile, action.selectedFile));
    expect(generator.next(fakeResponse).value).toEqual(
      put({
        type: types.SEND_ZIPPED_FILE_SUCCESS,
        selectedTemplate: decodeURIComponent(fakeResponse.response.metaEntity.htmlContent)
      })
    );
  });

  it('handles scenario when API result indicates an error', () => {
    const fakeErrorResponse = {
      status: { isError: true },
      message: 'Error processing file'
    };
    const generator = sendZippedFile(action);
    expect(generator.next().value).toEqual(call(api.sendZippedFile, action.selectedFile));
  });

  it('handles error thrown from api', () => {
    const error = new Error('Fetch failed');
    expectSaga(sendZippedFile, action)
      .provide([
        [
          call(api.sendZippedFile),
          throwError(error)
        ],
      ])
      .put({
        type: types.SEND_ZIPPED_FILE_FAILURE,
        data: ''
      })
      .run();
  });
});

describe('fetchUserList Saga', () => {
  
  it('handles successful fetching of user list', () => {
    const fakeResponse = {
      data: {
        result: [
          { id: 1, name: 'User One' },
          { id: 2, name: 'User Two' }
        ]
      }
    };
    const generator = fetchUserList();
    expect(generator.next().value).toEqual(call(api.getUserList));
    expect(generator.next(fakeResponse).value).toEqual(
      put({
        type: types.GET_USER_LIST_SUCCESS,
        data: fakeResponse.data.result
      })
    );
    expect(generator.next().done).toBe(true);
  });

  it('handles error thrown from API when fetching user list', () => {
    const error = new Error('Fetch failed');
    const generator = fetchUserList();
    expect(generator.next().value).toEqual(call(api.getUserList));
    expect(generator.throw(error).value).toEqual(
      put({
        type: types.GET_USER_LIST_FAILURE,
        data: error
      })
    );
    expect(generator.next().done).toBe(true);
  });
  it('handles error thrown from api', () => {
    const error = new Error('Fetch failed');
    expectSaga(fetchUserList)
      .provide([
        [
          call(api.getUserList),
          throwError(error)
        ],
      ])
      .put({
        type: types.GET_USER_LIST_FAILURE,
        data: error
      })
      .run();
  });
});

describe('deleteRcsTemplate Saga', () => {
  const action = { templateName: 'test-rcs-template' };

  it('handles successful deletion of RCS template', () => {
    const fakeResponse = {
      success: true,
      response: { message: 'Template deleted successfully' }
    };
    const generator = deleteRcsTemplate(action);
    expect(generator.next().value).toEqual(call(api.deleteRcsTemplate, action));
    expect(generator.next(fakeResponse).value).toEqual(
      put({
        type: types.DELETE_RCS_TEMPLATE_SUCCESS,
        data: fakeResponse.response,
      })
    );
    expect(generator.next().done).toBe(true);
  });

  it('handles API response with success: false', () => {
    const fakeErrorResponse = {
      success: false,
      message: 'Template not found',
    };
    const generator = deleteRcsTemplate(action);
    expect(generator.next().value).toEqual(call(api.deleteRcsTemplate, action));
    expect(generator.next(fakeErrorResponse).value).toEqual(
      put({
        type: types.DELETE_RCS_TEMPLATE_FAILURE,
        error: fakeErrorResponse.message,
      })
    );
    expect(generator.next().done).toBe(true);
  });

  it('handles API response with success: false and no message', () => {
    const fakeErrorResponse = {
      success: false
    };
    const generator = deleteRcsTemplate(action);
    expect(generator.next().value).toEqual(call(api.deleteRcsTemplate, action));
    expect(generator.next(fakeErrorResponse).value).toEqual(
      put({
        type: types.DELETE_RCS_TEMPLATE_FAILURE,
        error: fakeErrorResponse,
      })
    );
    expect(generator.next().done).toBe(true);
  });

  it('handles error thrown from API', () => {
    const error = new Error('Network error');
    const generator = deleteRcsTemplate(action);
    expect(generator.next().value).toEqual(call(api.deleteRcsTemplate, action));
    expect(generator.throw(error).value).toEqual(
      put({
        type: types.DELETE_RCS_TEMPLATE_FAILURE,
        error: error.message,
      })
    );
    expect(generator.next().done).toBe(true);
  });

  it('handles error thrown from API with no message', () => {
    const error = { code: 500 };
    const generator = deleteRcsTemplate(action);
    expect(generator.next().value).toEqual(call(api.deleteRcsTemplate, action));
    expect(generator.throw(error).value).toEqual(
      put({
        type: types.DELETE_RCS_TEMPLATE_FAILURE,
        error: error
      })
    );
    expect(generator.next().done).toBe(true);
  });

  it('handles error thrown from api using expectSaga', () => {
    const error = new Error('Delete failed');
    expectSaga(deleteRcsTemplate, action)
      .provide([
        [
          matchers.call.fn(api.deleteRcsTemplate),
          throwError(error),
        ],
      ])
      .put({
        type: types.DELETE_RCS_TEMPLATE_FAILURE,
        error: error.message,
      })
      .run();
  });
});

describe('watchDeleteRcsTemplate saga', () => {
  const generator = watchDeleteRcsTemplate();
  it('should call watchers functions', () => {
    expect(generator.next().value).toEqual(
      takeLatest(types.DELETE_RCS_TEMPLATE_REQUEST, deleteRcsTemplate),
    );
  });
});

describe('deleteTemplate saga', () => {
  const channel = { channel: 'RCS' };
  const id = 'tpl-1';
  it('handles success', () => {
    const apiResult = { response: { ok: true } };
    const generator = deleteTemplate(channel, id);
    expect(generator.next().value).toEqual(call(api.deleteTemplate, channel, id));
    expect(generator.next(apiResult).value).toEqual(put({
      type: types.DELETE_TEMPLATE_SUCCESS,
      data: apiResult.response,
    }));
    expect(generator.next().done).toBe(true);
  });

  it('handles failure', () => {
    const error = new Error('fail');
    const generator = deleteTemplate(channel, id);
    expect(generator.next().value).toEqual(call(api.deleteTemplate, channel, id));
    expect(generator.throw(error).value).toEqual(put({
      type: types.DELETE_TEMPLATE_FAILURE,
      error,
    }));
    expect(generator.next().done).toBe(true);
  });
});

describe('watchDeleteTemplate saga', () => {
  const generator = watchDeleteTemplate();
  it('should call watchers functions', () => {
    expect(generator.next().value).toEqual(
      takeLatest(types.DELETE_TEMPLATE_REQUEST, deleteTemplate),
    );
  });
});

describe('getTemplateDetails saga', () => {
  it('handles success', () => {
    const id = '1';
    const channel = { channel: 'RCS' };
    const apiResult = { response: { id: 1 } };
    const generator = getTemplateDetails(id, channel);
    expect(generator.next().value).toEqual(call(api.getTemplateDetails, id, channel));
    expect(generator.next(apiResult).value).toEqual(put({
      type: types.GET_TEMPLATE_DETAILS_SUCCESS,
      data: apiResult.response,
    }));
    expect(generator.next().done).toBe(true);
  });

  it('handles failure', () => {
    const id = '1';
    const channel = { channel: 'RCS' };
    const error = new Error('oops');
    const generator = getTemplateDetails(id, channel);
    expect(generator.next().value).toEqual(call(api.getTemplateDetails, id, channel));
    expect(generator.throw(error).value).toEqual(put({
      type: types.GET_TEMPLATE_DETAILS_FAILURE,
      error,
    }));
    expect(generator.next().done).toBe(true);
  });
});

describe('watchGetTemplateDetails saga', () => {
  const generator = watchGetTemplateDetails();
  it('should call watchers functions', () => {
    expect(generator.next().value).toEqual(
      takeLatest(types.GET_TEMPLATE_DETAILS_REQUEST, getTemplateDetails),
    );
  });
});

describe('watchFetchWeCrmAccounts saga', () => {
  const generator = watchFetchWeCrmAccounts();
  it('should call watchers functions', () => {
    expect(generator.next().value).toEqual(
      takeLatest(types.GET_WECRM_ACCOUNTS_REQUEST, fetchWeCrmAccounts),
    );
  });
});

describe('watchSendingFile saga', () => {
  const generator = watchSendingFile();
  it('should call watchers functions', () => {
    expect(generator.next().value).toEqual(
      takeLatest(types.SEND_ZIPPED_FILE_REQUEST, sendZippedFile),
    );
  });
});

describe('watchForGetTemplateInfoById saga', () => {
  it('should call getTemplateInfoById when preview flag is true (preview request)', () => {
    const action = {
      type: ZALO_TEMPLATE_INFO_REQUEST,
      payload: {
        username: 'testUser',
        oa_id: '12345',
        token: 'testToken',
        host: 'testHost',
        id: 'template123',
        preview: true,
      },
    };

    expectSaga(watchForGetTemplateInfoById)
      .provide([
        [matchers.call.fn(getTemplateInfoById), undefined],
      ])
      .dispatch(action)
      .call(getTemplateInfoById, action)
      .run();
  });

  it('should not call getTemplateInfoById when preview flag is false or missing (edit request)', () => {
    const action = {
      type: ZALO_TEMPLATE_INFO_REQUEST,
      payload: {
        username: 'testUser',
        oa_id: '12345',
        token: 'testToken',
        host: 'testHost',
        id: 'template123',
        // preview flag is missing (undefined)
      },
    };

    expectSaga(watchForGetTemplateInfoById)
      .provide([
        [matchers.call.fn(getTemplateInfoById), undefined],
      ])
      .dispatch(action)
      .not.call(getTemplateInfoById, action)
      .run();
  });
});

describe('watchGetOrgLevelCampaignSettings saga', () => {
  it('should take latest GET_ORG_LEVEL_CAMPAIGN_SETTINGS_REQUEST', () => {
    const generator = watchGetOrgLevelCampaignSettings();
    expect(generator.next().value).toEqual(
      takeLatest(types.GET_ORG_LEVEL_CAMPAIGN_SETTINGS_REQUEST, getOrgLevelCampaignSettings),
    );
  });
});

describe('watchGetUserList saga', () => {
  it('should take latest GET_USER_LIST_REQUEST', () => {
    const generator = watchGetUserList();
    expect(generator.next().value).toEqual(
      takeLatest(types.GET_USER_LIST_REQUEST, fetchUserList),
    );
  });
});

describe('getAllTemplates wechat channel', () => {
  it('should combine mapped and richmedia and sort by Most Recent (desc updatedAt)', () => {
    const wechatChannel = {
      channel: 'wechat',
      queryParams: { page: 1, sortBy: 'Most Recent' },
    };
    const apiResult = {
      response: {
        mapped: [{ name: 'Template B', updatedAt: '2020-01-01' }],
        richmedia: [{ name: 'Template A', updatedAt: '2023-06-01' }],
        unMapped: {},
      },
    };
    const generator = getAllTemplates(wechatChannel);
    generator.next(); // yield call(Api.getAllTemplates, ...)
    const putEffect = generator.next(apiResult).value;
    // Most recent first: Template A (2023) before Template B (2020)
    expect(putEffect).toEqual(
      put({
        type: types.GET_ALL_TEMPLATES_SUCCESS,
        data: {
          templates: [
            { name: 'Template A', updatedAt: '2023-06-01' },
            { name: 'Template B', updatedAt: '2020-01-01' },
          ],
        },
        weCRMTemplate: {},
        isReset: true,
      }),
    );
    expect(generator.next().done).toBe(true);
  });

  it('should combine mapped and richmedia and sort Alphabetically for wechat channel', () => {
    const wechatChannel = {
      channel: 'wechat',
      queryParams: { page: 1, sortBy: 'Alphabetically' },
    };
    const apiResult = {
      response: {
        mapped: [{ name: 'Z Template' }, { name: 'A Template' }],
        richmedia: [],
        unMapped: {},
      },
    };
    const generator = getAllTemplates(wechatChannel);
    generator.next(); // yield call(Api.getAllTemplates, ...)
    // b.name - a.name on strings gives NaN; sort order is unchanged from original
    expect(generator.next(apiResult).value).toEqual(
      put({
        type: types.GET_ALL_TEMPLATES_SUCCESS,
        data: { templates: [{ name: 'Z Template' }, { name: 'A Template' }] },
        weCRMTemplate: {},
        isReset: true,
      }),
    );
    expect(generator.next().done).toBe(true);
  });

  it('should replace COPY_OF string in template names when intlCopyOf is set', () => {
    const channelWithCopy = {
      channel: 'email',
      queryParams: { page: 1 },
      intlCopyOf: 'Copia de',
    };
    const apiResult = {
      response: {
        templates: [{ name: 'Copy of Template 1' }, { name: 'Regular Template' }],
        unMapped: {},
      },
    };
    const generator = getAllTemplates(channelWithCopy);
    generator.next();
    const putEffect = generator.next(apiResult).value;
    expect(putEffect).toEqual(
      put({
        type: types.GET_ALL_TEMPLATES_SUCCESS,
        data: {
          templates: [
            { name: 'Copia de Template 1' },
            { name: 'Regular Template' },
          ],
          unMapped: {},
        },
        weCRMTemplate: {},
        isReset: true,
      }),
    );
    expect(generator.next().done).toBe(true);
  });
});
describe('archiveTemplateSaga', () => {
  it('should call archiveTemplate API then dispatch ARCHIVE_TEMPLATE_SUCCESS with payload', () => {
    const successMessage = 'Template archived successfully';
    const description = 'Template name Test';
    const action = { channel: 'EMAIL', id: 'id1', successMessage, description };
    const gen = archiveTemplateSaga(action);
    // first yield: call archiveTemplate
    gen.next();
    // second yield: put ARCHIVE_TEMPLATE_SUCCESS with id and success payload
    const successStep = gen.next();
    expect(successStep.value).toEqual(put({ type: types.ARCHIVE_TEMPLATE_SUCCESS, id: 'id1', successMessage, description }));
    // done — no more yields
    expect(gen.next().done).toBe(true);
  });

  it('should dispatch ARCHIVE_TEMPLATE_FAILURE and call CapNotification.error on error', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const action = { channel: 'EMAIL', id: 'id1', successMessage: 'archived' };
    const gen = archiveTemplateSaga(action);
    gen.next(); // call archiveTemplate
    const error = new Error('archive failed');
    expect(gen.throw(error).value).toEqual(put({ type: types.ARCHIVE_TEMPLATE_FAILURE, error }));
    const done = gen.next();
    expect(done.done).toBe(true);
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'archive failed' });
  });
});

describe('unarchiveTemplateSaga', () => {
  it('should call unarchiveTemplate API then dispatch UNARCHIVE_TEMPLATE_SUCCESS with payload', () => {
    const successMessage = 'Template unarchived successfully';
    const description = 'Template name Test';
    const action = { channel: 'EMAIL', id: 'id1', successMessage, description };
    const gen = unarchiveTemplateSaga(action);
    // first yield: call unarchiveTemplate
    gen.next();
    // second yield: put UNARCHIVE_TEMPLATE_SUCCESS with id and success payload
    const successStep = gen.next();
    expect(successStep.value).toEqual(put({ type: types.UNARCHIVE_TEMPLATE_SUCCESS, id: 'id1', successMessage, description }));
    expect(gen.next().done).toBe(true);
  });

  it('should dispatch UNARCHIVE_TEMPLATE_FAILURE and call CapNotification.error on error', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const action = { channel: 'EMAIL', id: 'id1', successMessage: 'unarchived' };
    const gen = unarchiveTemplateSaga(action);
    gen.next(); // call unarchiveTemplate
    const error = new Error('unarchive failed');
    expect(gen.throw(error).value).toEqual(put({ type: types.UNARCHIVE_TEMPLATE_FAILURE, error }));
    const done = gen.next();
    expect(done.done).toBe(true);
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'unarchive failed' });
  });
});

describe('bulkArchiveTemplatesSaga', () => {
  it('should call bulkArchiveTemplates API then dispatch BULK_ARCHIVE_SUCCESS with count', () => {
    const successMessage = (count) => `${count} templates archived`;
    const action = { channel: 'EMAIL', ids: ['id1', 'id2'], successMessage };
    const gen = bulkArchiveTemplatesSaga(action);
    // first yield: call bulkArchiveTemplates
    gen.next();
    // second yield: put BULK_ARCHIVE_SUCCESS with successMessage and count
    const successStep = gen.next({ response: { modifiedCount: 2 } });
    expect(successStep.value).toEqual(put({ type: types.BULK_ARCHIVE_SUCCESS, successMessage, count: 2 }));
    expect(gen.next().done).toBe(true);
  });

  it('should use ids.length as count fallback when modifiedCount is missing', () => {
    const successMessage = (count) => `${count} templates archived`;
    const action = { channel: 'EMAIL', ids: ['id1', 'id2', 'id3'], successMessage };
    const gen = bulkArchiveTemplatesSaga(action);
    gen.next();
    const successStep = gen.next({ response: {} });
    expect(successStep.value).toEqual(put({ type: types.BULK_ARCHIVE_SUCCESS, successMessage, count: 3 }));
  });

  it('should dispatch BULK_ARCHIVE_FAILURE and call CapNotification.error on error', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const action = { channel: 'EMAIL', ids: ['id1'] };
    const gen = bulkArchiveTemplatesSaga(action);
    gen.next(); // call bulkArchiveTemplates
    const error = new Error('bulk archive failed');
    expect(gen.throw(error).value).toEqual(put({ type: types.BULK_ARCHIVE_FAILURE, error }));
    const done = gen.next();
    expect(done.done).toBe(true);
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'bulk archive failed' });
  });
});

describe('bulkUnarchiveTemplatesSaga', () => {
  it('should call bulkUnarchiveTemplates API then dispatch BULK_UNARCHIVE_SUCCESS with count', () => {
    const successMessage = (count) => `${count} templates unarchived`;
    const action = { channel: 'EMAIL', ids: ['id1', 'id2'], successMessage };
    const gen = bulkUnarchiveTemplatesSaga(action);
    // first yield: call bulkUnarchiveTemplates
    gen.next();
    // second yield: put BULK_UNARCHIVE_SUCCESS with successMessage and count
    const successStep = gen.next({ response: { modifiedCount: 2 } });
    expect(successStep.value).toEqual(put({ type: types.BULK_UNARCHIVE_SUCCESS, successMessage, count: 2 }));
    expect(gen.next().done).toBe(true);
  });

  it('should use ids.length as count fallback when modifiedCount is missing', () => {
    const successMessage = (count) => `${count} templates unarchived`;
    const action = { channel: 'EMAIL', ids: ['id1', 'id2'], successMessage };
    const gen = bulkUnarchiveTemplatesSaga(action);
    gen.next();
    const successStep = gen.next({ response: {} });
    expect(successStep.value).toEqual(put({ type: types.BULK_UNARCHIVE_SUCCESS, successMessage, count: 2 }));
  });

  it('should dispatch BULK_UNARCHIVE_FAILURE and call CapNotification.error on error', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const action = { channel: 'EMAIL', ids: ['id1'] };
    const gen = bulkUnarchiveTemplatesSaga(action);
    gen.next(); // call bulkUnarchiveTemplates
    const error = new Error('bulk unarchive failed');
    expect(gen.throw(error).value).toEqual(put({ type: types.BULK_UNARCHIVE_FAILURE, error }));
    const done = gen.next();
    expect(done.done).toBe(true);
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'bulk unarchive failed' });
  });
});

describe('archive watcher sagas', () => {
  it('watchArchiveTemplate should take latest ARCHIVE_TEMPLATE_REQUEST', () => {
    const gen = watchArchiveTemplate();
    expect(gen.next().value).toEqual(takeLatest(types.ARCHIVE_TEMPLATE_REQUEST, archiveTemplateSaga));
  });

  it('watchUnarchiveTemplate should take latest UNARCHIVE_TEMPLATE_REQUEST', () => {
    const gen = watchUnarchiveTemplate();
    expect(gen.next().value).toEqual(takeLatest(types.UNARCHIVE_TEMPLATE_REQUEST, unarchiveTemplateSaga));
  });

  it('watchBulkArchive should take latest BULK_ARCHIVE_REQUEST', () => {
    const gen = watchBulkArchive();
    expect(gen.next().value).toEqual(takeLatest(types.BULK_ARCHIVE_REQUEST, bulkArchiveTemplatesSaga));
  });

  it('watchBulkUnarchive should take latest BULK_UNARCHIVE_REQUEST', () => {
    const gen = watchBulkUnarchive();
    expect(gen.next().value).toEqual(takeLatest(types.BULK_UNARCHIVE_REQUEST, bulkUnarchiveTemplatesSaga));
  });
});

describe('fetchWeCrmAccounts failure', () => {
  it('should dispatch GET_WECRM_ACCOUNTS_FAILURE on error', () => {
    const action = { source: 'test-source' };
    const gen = fetchWeCrmAccounts(action);
    gen.next(); // call fetchWeCrmAccounts
    const error = new Error('fetch failed');
    expect(gen.throw(error).value).toEqual(
      put({ type: types.GET_WECRM_ACCOUNTS_FAILURE, data: error })
    );
  });
});

describe('sendZippedFile saga', () => {
  it('should call errorHandler and return when result.status.isError is true', () => {
    const mockErrorHandler = jest.fn();
    const mockSuccessHandler = jest.fn();
    const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
    const gen = sendZippedFile(action);
    gen.next(); // call Api.sendZippedFile
    const result = { status: { isError: true }, message: 'upload error' };
    // advance past yield call — enters isError branch
    const step = gen.next(result);
    // errorMessage = result.message; yield errorHandler(errorMessage)
    // errorHandler returns undefined so yielded value is undefined
    expect(step.value).toBeUndefined(); // yield errorHandler('upload error')
    const done = gen.next(); // return
    expect(done.done).toBe(true);
  });

  it('should call successHandler after successful upload', () => {
    const mockErrorHandler = jest.fn();
    const mockSuccessHandler = jest.fn();
    const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
    const gen = sendZippedFile(action);
    gen.next(); // call Api.sendZippedFile
    const result = {
      status: { isError: false },
      response: { metaEntity: { htmlContent: encodeURIComponent('<html></html>') } },
    };
    gen.next(result); // advance past isError check, put SEND_ZIPPED_FILE_SUCCESS
    gen.next(); // advance to successHandler yield
    const done = gen.next();
    expect(done.done).toBe(true);
  });

  it('should call errorHandler and put SEND_ZIPPED_FILE_FAILURE on exception', () => {
    const mockErrorHandler = jest.fn();
    const mockSuccessHandler = jest.fn();
    const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
    const gen = sendZippedFile(action);
    gen.next(); // call Api.sendZippedFile
    const error = new Error('network error');
    // throw error — enters catch: yield errorHandler()
    const step1 = gen.throw(error);
    // errorHandler returns undefined so yielded value is undefined
    expect(step1.value).toBeUndefined(); // yield errorHandler()
    // yield put SEND_ZIPPED_FILE_FAILURE
    const step2 = gen.next();
    expect(step2.value).toEqual(
      put({ type: types.SEND_ZIPPED_FILE_FAILURE, data: '' })
    );
  });
});

describe('archive sagas - CapNotification.error coverage', () => {
  it('archiveTemplateSaga should call CapNotification.error on failure', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = archiveTemplateSaga({ channel: 'EMAIL', id: 'id1', templateName: 'Test' });
    gen.next(); // call archiveTemplate
    const error = new Error('archive failed');
    gen.throw(error); // put ARCHIVE_TEMPLATE_FAILURE
    expect(CapNotification.error).toBeDefined();
  });

  it('unarchiveTemplateSaga should call CapNotification.error on failure', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = unarchiveTemplateSaga({ channel: 'EMAIL', id: 'id1', templateName: 'Test' });
    gen.next(); // call unarchiveTemplate
    const error = new Error('unarchive failed');
    gen.throw(error); // put UNARCHIVE_TEMPLATE_FAILURE
    expect(CapNotification.error).toBeDefined();
  });

  it('bulkArchiveTemplatesSaga should call CapNotification.error on failure', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = bulkArchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
    gen.next(); // call bulkArchiveTemplates
    const error = new Error('bulk archive failed');
    gen.throw(error); // put BULK_ARCHIVE_FAILURE
    expect(CapNotification.error).toBeDefined();
  });

  it('bulkUnarchiveTemplatesSaga should call CapNotification.error on failure', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = bulkUnarchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
    gen.next(); // call bulkUnarchiveTemplates
    const error = new Error('bulk unarchive failed');
    gen.throw(error); // put BULK_UNARCHIVE_FAILURE
    expect(CapNotification.error).toBeDefined();
  });
});

describe('archive sagas - first yield and fallback message coverage', () => {
  it('archiveTemplateSaga first yield should be a redux-saga call effect', () => {
    const gen = archiveTemplateSaga({ channel: 'EMAIL', id: 'id1', successMessage: 'ok' });
    const firstStep = gen.next();
    expect(firstStep.value).toHaveProperty('@@redux-saga/IO', true);
    expect(firstStep.value).toHaveProperty('CALL');
    expect(firstStep.value.CALL.args).toEqual([{ channel: 'EMAIL', id: 'id1' }]);
  });

  it('archiveTemplateSaga error with no message property should use fallback string', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = archiveTemplateSaga({ channel: 'EMAIL', id: 'id1', successMessage: 'ok' });
    gen.next(); // call archiveTemplate
    const error = {}; // no .message
    gen.throw(error); // put ARCHIVE_TEMPLATE_FAILURE
    gen.next(); // CapNotification.error runs
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to archive template' });
  });

  it('unarchiveTemplateSaga first yield should be a redux-saga call effect', () => {
    const gen = unarchiveTemplateSaga({ channel: 'EMAIL', id: 'id1', successMessage: 'ok' });
    const firstStep = gen.next();
    expect(firstStep.value).toHaveProperty('@@redux-saga/IO', true);
    expect(firstStep.value).toHaveProperty('CALL');
    expect(firstStep.value.CALL.args).toEqual([{ channel: 'EMAIL', id: 'id1' }]);
  });

  it('unarchiveTemplateSaga error with no message property should use fallback string', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = unarchiveTemplateSaga({ channel: 'EMAIL', id: 'id1', successMessage: 'ok' });
    gen.next(); // call unarchiveTemplate
    const error = {}; // no .message
    gen.throw(error); // put UNARCHIVE_TEMPLATE_FAILURE
    gen.next(); // CapNotification.error runs
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to unarchive template' });
  });

  it('bulkArchiveTemplatesSaga error with no message property should use fallback string', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = bulkArchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
    gen.next(); // call bulkArchiveTemplates
    const error = {}; // no .message
    gen.throw(error); // put BULK_ARCHIVE_FAILURE
    gen.next(); // CapNotification.error runs
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to archive templates' });
  });

  it('bulkArchiveTemplatesSaga with no successMessage stores undefined successMessage in BULK_ARCHIVE_SUCCESS', () => {
    const gen = bulkArchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1', 'id2'] });
    gen.next(); // call bulkArchiveTemplates
    const successStep = gen.next({ response: { modifiedCount: 2 } });
    // successMessage is undefined (not provided); component will use fallback
    expect(successStep.value).toEqual(put({ type: types.BULK_ARCHIVE_SUCCESS, successMessage: undefined, count: 2 }));
  });

  it('bulkArchiveTemplatesSaga with no modifiedCount in response falls back to ids.length', () => {
    const gen = bulkArchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1', 'id2', 'id3'] });
    gen.next(); // call bulkArchiveTemplates
    const successStep = gen.next({ response: {} }); // no modifiedCount
    expect(successStep.value).toEqual(put({ type: types.BULK_ARCHIVE_SUCCESS, successMessage: undefined, count: 3 }));
  });

  it('bulkUnarchiveTemplatesSaga error with no message property should use fallback string', () => {
    const { CapNotification } = require('@capillarytech/cap-ui-library');
    const gen = bulkUnarchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
    gen.next(); // call bulkUnarchiveTemplates
    const error = {}; // no .message
    gen.throw(error); // put BULK_UNARCHIVE_FAILURE
    gen.next(); // CapNotification.error runs
    expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to unarchive templates' });
  });

  it('bulkUnarchiveTemplatesSaga with no successMessage stores undefined successMessage in BULK_UNARCHIVE_SUCCESS', () => {
    const gen = bulkUnarchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1', 'id2'] });
    gen.next(); // call bulkUnarchiveTemplates
    const successStep = gen.next({ response: { modifiedCount: 2 } });
    expect(successStep.value).toEqual(put({ type: types.BULK_UNARCHIVE_SUCCESS, successMessage: undefined, count: 2 }));
  });

  it('bulkUnarchiveTemplatesSaga with no modifiedCount in response falls back to ids.length', () => {
    const gen = bulkUnarchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1', 'id2', 'id3'] });
    gen.next(); // call bulkUnarchiveTemplates
    const successStep = gen.next({ response: {} }); // no modifiedCount
    expect(successStep.value).toEqual(put({ type: types.BULK_UNARCHIVE_SUCCESS, successMessage: undefined, count: 3 }));
  });
});

describe('watchGetSenderDetails', () => {
  it('should take latest GET_SENDER_DETAILS_REQUEST', () => {
    const gen = watchGetSenderDetails();
    expect(gen.next().value).toEqual(takeLatest(types.GET_SENDER_DETAILS_REQUEST, getSenderDetails));
  });
});

describe('v2TemplateSaga', () => {
  it('should yield all watchers including archive watchers', () => {
    const gen = v2TemplateSaga();
    const step = gen.next();
    // all() returns an IO object with an ALL key containing the array of effects
    expect(step.value).toHaveProperty('@@redux-saga/IO', true);
    expect(step.value).toHaveProperty('ALL');
    expect(step.value.ALL).toHaveLength(14);
    expect(step.done).toBe(false);
  });
});

describe('v2TemplateSagaWatchGetDefaultBeeTemplates', () => {
  it('should yield all with watchSendingFile and watchGetDefaultBeeTemplates', () => {
    const gen = v2TemplateSagaWatchGetDefaultBeeTemplates();
    const step = gen.next();
    expect(step.value).toHaveProperty('@@redux-saga/IO', true);
    expect(step.value).toHaveProperty('ALL');
    expect(step.value.ALL).toHaveLength(2);
    expect(step.done).toBe(false);
  });
});
