/**
 * DeliverySettingsSection: user journeys after the marketer has added channel content.
 * The section loads domainProperties for those channels, shows a summary row, and opens
 * SenderDetails to adjust senders. Viber can fall back to WeCRM when DGM returns empty contactInfo.
 */
import React from 'react';
import {
  render, screen, waitFor, within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { IntlProvider } from 'react-intl';
import DeliverySettingsSection from '../DeliverySettingsSection';
import { getDomainProperties, fetchWeCrmAccounts } from '../../../../../services/api';
import { loadItem } from '../../../../../services/localStorageApi';
import { findVisibleSelectOption } from '../../../../../utils/test-utils';

jest.mock('../../../../../services/api', () => ({
  getDomainProperties: jest.fn(),
  fetchWeCrmAccounts: jest.fn(),
}));

jest.mock('../../../../../services/localStorageApi', () => ({
  loadItem: jest.fn(),
}));

const WABA_ID = 'waba-test-1';

/** Typical `getDomainProperties` payloads per channel (merged when testing multi-channel). */
const apiEntity = {
  SMS: {
    SMS: [
      {
        domainProperties: {
          id: 'sms-dom-1',
          domainName: 'SMS gateway',
          contactInfo: [
            {
              type: 'gsm_sender_id', value: '+1002003001', valid: true, default: true,
            },
            {
              type: 'gsm_sender_id', value: '+1002003002', valid: true, default: false,
            },
          ],
        },
      },
    ],
  },
  EMAIL: {
    EMAIL: [
      {
        domainProperties: {
          id: 'em-1',
          domainName: 'Email gateway',
          contactInfo: [
            {
              type: 'sender_id',
              value: 'notify@brand.com',
              label: 'Brand',
              valid: true,
              default: true,
            },
          ],
        },
      },
    ],
  },
  VIBER: {
    VIBER: [
      {
        domainProperties: {
          id: 'vb-1',
          domainName: 'Viber gateway',
          contactInfo: [
            {
              type: 'gsm_sender_id', value: 'viber-sender-99', valid: true, default: true,
            },
          ],
        },
      },
    ],
  },
  ZALO: {
    ZALO: [
      {
        domainProperties: {
          id: 'zl-1',
          domainName: 'Zalo OA',
          contactInfo: [
            {
              type: 'gsm_sender_id', value: 'zalo-sender-77', valid: true, default: true,
            },
          ],
        },
      },
    ],
  },
  LINE: {
    LINE: [
      {
        domainProperties: {
          id: 'ln-1',
          domainName: 'LINE @brand',
          contactInfo: [
            {
              type: 'gsm_sender_id', value: 'line@account-id', valid: true, default: true,
            },
          ],
        },
      },
    ],
  },
  RCS: {
    RCS: [
      {
        domainProperties: {
          id: 'rcs-1',
          domainName: 'RCS brand',
          contactInfo: [
            {
              type: 'gsm_sender_id', value: '+12025550123', valid: true, default: true,
            },
          ],
        },
      },
    ],
  },
  WHATSAPP: {
    WHATSAPP: [
      {
        domainProperties: {
          id: 'wa-dom-1',
          domainName: 'WA business',
          connectionProperties: { sourceAccountIdentifier: WABA_ID },
          contactInfo: [
            {
              type: 'gsm_sender_id', value: '+441234567890', valid: true, default: true,
            },
          ],
        },
      },
    ],
  },
};

function renderSection(props = {}) {
  const {
    contentItems = [{ channel: 'SMS' }],
    deliverySettingsData = {},
    deliverySetting = {},
    onDeliverySettingChange,
  } = props;
  return render(
    <IntlProvider locale="en" messages={{}} defaultLocale="en">
      <DeliverySettingsSection
        contentItems={contentItems}
        deliverySettingsData={deliverySettingsData}
        deliverySetting={deliverySetting}
        onDeliverySettingChange={onDeliverySettingChange}
      />
    </IntlProvider>,
  );
}

async function openSenderDetailsFromSummary() {
  const heading = await screen.findByText('Sender details');
  const row = heading.closest('.delivery-settings-section--clickable');
  await userEvent.click(row);
  await waitFor(() => {
    expect(document.querySelector('.sender-details')).toBeInTheDocument();
  });
}

describe('DeliverySettingsSection — marketer flows', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    loadItem.mockImplementation((key) => (key === 'ouId' || key === 'orgID' ? 'test-ou' : null));
    fetchWeCrmAccounts.mockResolvedValue({ response: [] });
  });

  describe('When the block should not appear', () => {
    it('hides delivery settings if the feature is not enabled for the flow', () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      renderSection({
        contentItems: [{ channel: 'SMS' }],
        deliverySettingsData: null,
      });

      expect(screen.queryByText('Sender details')).not.toBeInTheDocument();
      expect(getDomainProperties).not.toHaveBeenCalled();
    });

    it('hides when there is no content yet (nothing to derive channels from)', () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      renderSection({ contentItems: [], deliverySettingsData: {} });

      expect(screen.queryByText('Sender details')).not.toBeInTheDocument();
    });

    it('hides when every configured template is push/in-app (no delivery senders)', () => {
      getDomainProperties.mockResolvedValue({ entity: {} });

      renderSection({
        contentItems: [{ channel: 'MPUSH' }, { channel: 'INAPP' }],
        deliverySettingsData: {},
      });

      expect(screen.queryByText('Sender details')).not.toBeInTheDocument();
      expect(getDomainProperties).not.toHaveBeenCalled();
    });
  });

  describe('After domainProperties loads — summary strip', () => {
    it('shows Sender ID and the default SMS number from the gateway', async () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      renderSection({ contentItems: [{ channel: 'SMS' }] });

      await waitFor(() => {
        expect(screen.getByText('Sender details')).toBeInTheDocument();
        expect(screen.getByText('Sender ID')).toBeInTheDocument();
        expect(screen.getByText('+1002003001')).toBeInTheDocument();
      });
    });

    it('shows WhatsApp sender number and uses template WABA + display name from the creative', async () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.WHATSAPP });

      renderSection({
        contentItems: [
          {
            channel: 'WHATSAPP',
            templateData: { sourceAccountIdentifier: WABA_ID, accountName: 'Shop name' },
          },
        ],
      });

      await waitFor(() => {
        expect(screen.getByText('Sender number')).toBeInTheDocument();
        expect(screen.getByText('+441234567890')).toBeInTheDocument();
      });
    });

    it('lists one row per delivery channel when the creative is multi-channel', async () => {
      const merged = {
        ...apiEntity.SMS,
        ...apiEntity.EMAIL,
        ...apiEntity.VIBER,
      };
      getDomainProperties.mockResolvedValue({ entity: merged });

      renderSection({
        contentItems: [{ channel: 'SMS' }, { channel: 'EMAIL' }, { channel: 'VIBER' }],
      });

      await waitFor(() => {
        expect(screen.getByText('+1002003001')).toBeInTheDocument();
        expect(screen.getByText('notify@brand.com')).toBeInTheDocument();
        expect(screen.getByText('viber-sender-99')).toBeInTheDocument();
      });
      expect(screen.getAllByText('Sender ID').length).toBeGreaterThanOrEqual(3);
    });

    it('maps ZALO, LINE, and RCS labels the way the summary card expects', async () => {
      getDomainProperties.mockResolvedValue({
        entity: {
          ...apiEntity.ZALO,
          ...apiEntity.LINE,
          ...apiEntity.RCS,
        },
      });

      renderSection({
        contentItems: [{ channel: 'ZALO' }, { channel: 'LINE' }, { channel: 'RCS' }],
      });

      await waitFor(() => {
        expect(screen.getByText('zalo-sender-77')).toBeInTheDocument();
        expect(screen.getByText('Account')).toBeInTheDocument();
        expect(screen.getByText('line@account-id')).toBeInTheDocument();
        expect(screen.getByText('+12025550123')).toBeInTheDocument();
      });
    });
  });

  describe('API behaviour the UI must tolerate', () => {
    it('uses org unit from local storage (ouId, else orgID) when requesting gateways', async () => {
      loadItem.mockImplementation((key) => (key === 'orgID' ? 'fallback-org' : null));
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      renderSection({ contentItems: [{ channel: 'SMS' }] });

      await waitFor(() => expect(getDomainProperties).toHaveBeenCalledWith(['SMS'], 'fallback-org'));
    });

    it('accepts the whole response as the entity when `entity` is omitted (some clients)', async () => {
      getDomainProperties.mockResolvedValue(apiEntity.SMS);

      renderSection({ contentItems: [{ channel: 'SMS' }] });

      await waitFor(() => {
        expect(screen.getByText('+1002003001')).toBeInTheDocument();
      });
    });

    it('normalizes mixed-case channel keys from the API before parsing', async () => {
      getDomainProperties.mockResolvedValue({
        entity: {
          sms: apiEntity.SMS.SMS,
        },
      });

      renderSection({ contentItems: [{ channel: 'SMS' }] });

      await waitFor(() => {
        expect(screen.getByText('+1002003001')).toBeInTheDocument();
      });
    });

    it('shows only the heading when domainProperties fails (user still opens settings to fix)', async () => {
      getDomainProperties.mockRejectedValue(new Error('network'));

      renderSection({ contentItems: [{ channel: 'SMS' }] });

      await waitFor(() => {
        expect(screen.getByText('Sender details')).toBeInTheDocument();
      });
      expect(screen.queryByText('+1002003001')).not.toBeInTheDocument();
      expect(screen.queryByText('Sender ID')).not.toBeInTheDocument();
    });

    it('does not refetch domainProperties when the channel set is unchanged (parent re-render)', async () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      const { rerender } = render(
        <IntlProvider locale="en" messages={{}} defaultLocale="en">
          <DeliverySettingsSection
            contentItems={[{ channel: 'SMS', contentId: 'a' }]}
            deliverySettingsData={{}}
            deliverySetting={{}}
          />
        </IntlProvider>,
      );

      await waitFor(() => expect(getDomainProperties).toHaveBeenCalledTimes(1));
      await waitFor(() => expect(screen.getByText('+1002003001')).toBeInTheDocument());

      rerender(
        <IntlProvider locale="en" messages={{}} defaultLocale="en">
          <DeliverySettingsSection
            contentItems={[{ channel: 'SMS', contentId: 'b' }]}
            deliverySettingsData={{}}
            deliverySetting={{}}
          />
        </IntlProvider>,
      );

      await waitFor(() => expect(getDomainProperties).toHaveBeenCalledTimes(1));
    });
  });

  describe('Viber + WeCRM fallback (empty DGM contactInfo)', () => {
    const viberEmptyContact = {
      VIBER: [
        {
          domainProperties: {
            id: 'vb-empty',
            domainName: 'Viber DGM row',
            contactInfo: [],
          },
        },
        {
          domainProperties: {
            id: 'vb-2',
            domainName: 'Second row',
            contactInfo: [],
          },
        },
      ],
    };

    it('merges active WeCRM accounts into the first Viber gateway so senders appear in summary', async () => {
      fetchWeCrmAccounts.mockResolvedValue({
        response: [
          {
            id: 10,
            isActive: true,
            sourceAccountIdentifier: 'wecrm-src',
            name: 'WeCRM Viber',
          },
          {
            id: 11,
            isActive: false,
            sourceAccountIdentifier: 'inactive',
          },
        ],
      });
      getDomainProperties.mockResolvedValue({ entity: viberEmptyContact });

      renderSection({ contentItems: [{ channel: 'VIBER' }] });

      await waitFor(() => {
        expect(fetchWeCrmAccounts).toHaveBeenCalledWith('VIBER');
        expect(screen.getByText('wecrm-src')).toBeInTheDocument();
      });
    });

    it('uses configs.viber_account_name as label when the account has no display name', async () => {
      fetchWeCrmAccounts.mockResolvedValue({
        response: [
          {
            id: 20,
            isActive: true,
            sourceAccountIdentifier: 'id-20',
            configs: { viber_account_name: 'Config label' },
          },
        ],
      });
      getDomainProperties.mockResolvedValue({ entity: viberEmptyContact });

      renderSection({ contentItems: [{ channel: 'VIBER' }] });

      await waitFor(() => {
        expect(screen.getByText('id-20')).toBeInTheDocument();
      });
    });

    it('treats a single WeCRM object the same as a one-element list', async () => {
      fetchWeCrmAccounts.mockResolvedValue({
        response: { id: 30, isActive: true, sourceAccountIdentifier: 'solo' },
      });
      getDomainProperties.mockResolvedValue({ entity: viberEmptyContact });

      renderSection({ contentItems: [{ channel: 'VIBER' }] });

      await waitFor(() => {
        expect(screen.getByText('solo')).toBeInTheDocument();
      });
    });

    it('leaves summary empty-line when WeCRM returns nothing usable', async () => {
      fetchWeCrmAccounts.mockResolvedValue({ response: null });
      getDomainProperties.mockResolvedValue({ entity: viberEmptyContact });

      renderSection({ contentItems: [{ channel: 'VIBER' }] });

      await waitFor(() => {
        expect(screen.getByText('Sender details')).toBeInTheDocument();
      });
      expect(screen.queryByText('Sender ID')).not.toBeInTheDocument();
    });

    it('survives WeCRM errors without breaking the section', async () => {
      fetchWeCrmAccounts.mockRejectedValue(new Error('WeCRM down'));
      getDomainProperties.mockResolvedValue({ entity: viberEmptyContact });

      renderSection({ contentItems: [{ channel: 'VIBER' }] });

      await waitFor(() => {
        expect(screen.getByText('Sender details')).toBeInTheDocument();
      });
    });
  });

  describe('Saved delivery choices (edit existing creative)', () => {
    it('prefers persisted channelSetting values over the raw API defaults in the summary', async () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      renderSection({
        contentItems: [{ channel: 'SMS' }],
        deliverySetting: {
          channelSetting: {
            SMS: { gsmSenderId: '+saved-from-campaign', domainId: 'sms-dom-1' },
          },
        },
      });

      await waitFor(() => {
        expect(screen.getByText('+saved-from-campaign')).toBeInTheDocument();
      });
    });
  });

  describe('Opening SenderDetails and saving', () => {
    it('shows gateway errors inside the slidebox when SMS gateways are missing or misconfigured', async () => {
      getDomainProperties.mockResolvedValue({ entity: { SMS: [] } });

      renderSection({ contentItems: [{ channel: 'SMS' }] });

      await waitFor(() => expect(screen.getByText('Sender details')).toBeInTheDocument());
      await openSenderDetailsFromSummary();

      const slidebox = document.querySelector('.sender-details');
      await waitFor(() => {
        expect(
          within(slidebox).getByText(/Domain gateway id is not found for the selected channel/i),
        ).toBeInTheDocument();
      });
    });

    it('shows sender configuration error when the gateway exists but has no senders', async () => {
      getDomainProperties.mockResolvedValue({
        entity: {
          SMS: [
            {
              domainProperties: {
                id: 'sms-no-senders',
                domainName: 'Empty gateway',
                contactInfo: [],
              },
            },
          ],
        },
      });

      renderSection({ contentItems: [{ channel: 'SMS' }] });

      await waitFor(() => expect(screen.getByText('Sender details')).toBeInTheDocument());
      await openSenderDetailsFromSummary();

      const slidebox = document.querySelector('.sender-details');
      await waitFor(() => {
        expect(
          within(slidebox).getByText(/Selected domain gateway id is not correct/i),
        ).toBeInTheDocument();
      });
    });

    it('persists SMS sender changes back through onDeliverySettingChange', async () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });
      const onDeliverySettingChange = jest.fn();

      renderSection({
        contentItems: [{ channel: 'SMS' }],
        onDeliverySettingChange,
      });

      await waitFor(() => expect(screen.getByText('Sender details')).toBeInTheDocument());
      await openSenderDetailsFromSummary();

      const slidebox = document.querySelector('.sender-details');
      const combos = within(slidebox).getAllByRole('combobox');
      await userEvent.click(combos[1]);
      await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
      await userEvent.click(await findVisibleSelectOption('+1002003002'));

      const saveBtn = within(slidebox).getByRole('button', { name: /save changes/i });
      await waitFor(() => expect(saveBtn).not.toBeDisabled());
      await userEvent.click(saveBtn);

      expect(onDeliverySettingChange).toHaveBeenCalled();
      const payload = onDeliverySettingChange.mock.calls[0][0];
      expect(payload.channelSetting.SMS).toMatchObject({
        gsmSenderId: '+1002003002',
      });
    });

    it('does not throw if the parent omits onDeliverySettingChange and the user saves', async () => {
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      renderSection({
        contentItems: [{ channel: 'SMS' }],
        onDeliverySettingChange: undefined,
      });

      await waitFor(() => expect(screen.getByText('Sender details')).toBeInTheDocument());
      await openSenderDetailsFromSummary();

      const slidebox = document.querySelector('.sender-details');
      const combos = within(slidebox).getAllByRole('combobox');
      await userEvent.click(combos[1]);
      await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
      await userEvent.click(await findVisibleSelectOption('+1002003002'));
      const saveBtn = within(slidebox).getByRole('button', { name: /save changes/i });
      await waitFor(() => expect(saveBtn).not.toBeDisabled());
      await userEvent.click(saveBtn);
    });
  });

  describe('DeduplicationRef — skips re-fetch when data already loaded for the same channels', () => {
    it('does not issue a second network call when the same channels are re-rendered with the same deliverySettingsData', async () => {
      // Covers line 85-86: lastChannelKeyRef === deliveryChannelKey && domainPropertiesData already set
      getDomainProperties.mockResolvedValue({ entity: apiEntity.SMS });

      const { rerender } = render(
        <IntlProvider locale="en" messages={{}} defaultLocale="en">
          <DeliverySettingsSection
            contentItems={[{ channel: 'SMS', id: 'a' }]}
            deliverySettingsData={{}}
            deliverySetting={{}}
          />
        </IntlProvider>,
      );

      // Wait for the first fetch to complete and data to load
      await waitFor(() => expect(getDomainProperties).toHaveBeenCalledTimes(1));
      await waitFor(() => expect(screen.getByText('+1002003001')).toBeInTheDocument());

      // Re-render with the same SMS channel — should hit the dedup branch, not fetch again
      rerender(
        <IntlProvider locale="en" messages={{}} defaultLocale="en">
          <DeliverySettingsSection
            contentItems={[{ channel: 'SMS', id: 'b' }]}
            deliverySettingsData={{}}
            deliverySetting={{}}
          />
        </IntlProvider>,
      );

      // getDomainProperties should still only have been called once
      await waitFor(() => expect(getDomainProperties).toHaveBeenCalledTimes(1));
    });
  });

});
