import React, { useState } from 'react';
import { fireEvent, screen, within } from '@testing-library/react';
import { createIdentity, renderInAppContext } from '@theorchard/suite-testing';
import * as storesQuery from 'src/apollo/queries/stores';
import * as transactionTypeQueries from 'src/apollo/queries/transaction-types';
import { ABACUS_PROFILE } from 'src/constants';
import SetupTermsStep, {
    buildDispatch,
} from '../create-transfer/setup-terms-step';
import type { TransferTermDraft } from '../create-transfer/setup-terms-step';
import type { GetStoresQuery } from 'src/apollo/queries/__generated__/stores';
import type { AbacusDistroContractTermCondition } from 'src/types/abacus-contract-term';

const identity = createIdentity({ profileType: ABACUS_PROFILE });

const makeCondition = (
    overrides: Partial<AbacusDistroContractTermCondition> = {}
): AbacusDistroContractTermCondition => ({
    contractTermConditionId: null,
    contractTermConditionName: null,
    contractTermId: 'term-1',
    priority: 1,
    termRate: null,
    conditions: { countries: [], stores: [], transactionTypes: [] },
    ...overrides,
});

const makeTerm = (
    overrides: Partial<TransferTermDraft> = {}
): TransferTermDraft => ({
    id: 'term-1',
    type: 'PRODUCT',
    name: 'Product',
    attachments: [],
    conditions: [makeCondition()],
    ...overrides,
});

const project = {
    id: 'proj-1',
    name: 'My Album',
    upcs: [{ upc: '123', productName: 'My Album', tracks: [] }],
};

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};

// Stateful harness: SetupTermsStep is controlled, so a real terms array is
// needed for an added term to actually render (and receive focus).
const TermsHarness: React.FC = () => {
    const [terms, setTerms] = useState<TransferTermDraft[]>([]);
    return (
        <SetupTermsStep
            project={project}
            terms={terms}
            termsMode="custom"
            onTermsChange={setTerms}
            onTermsModeChange={noop}
        />
    );
};

const mockHooks = () => {
    jest.spyOn(storesQuery, 'useStoreList').mockReturnValue({
        data: {
            deliveryStoresV2: { items: [{ id: '1', name: 'Spotify' }] },
        } as unknown as GetStoresQuery,
        loading: false,
    });
    jest.spyOn(transactionTypeQueries, 'useTransactionTypes').mockReturnValue({
        data: {
            transactionTypes: [
                { txnTypeId: '1', txnTypeCode: 'DL', txnTypeName: 'Download' },
            ],
        },
        loading: false,
    });
    jest.spyOn(
        transactionTypeQueries,
        'useTransactionTypeGroups'
    ).mockReturnValue({
        data: { transactionTypeGroups: [] },
        loading: false,
    });
};

describe('SetupTermsStep', () => {
    beforeEach(mockHooks);
    afterEach(jest.restoreAllMocks);

    it('renders condition rows in custom mode', () => {
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[makeTerm()]}
                termsMode="custom"
                onTermsChange={noop}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        expect(
            screen.getByTestId('contractTermConditionRow')
        ).toBeInTheDocument();
    });

    it('uses the redesigned method-selector copy', () => {
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[]}
                termsMode="custom"
                onTermsChange={noop}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        expect(
            screen.getByText('Select your preferred method for managing terms')
        ).toBeInTheDocument();
        // Appears on both the radio label and the section subheading.
        expect(
            screen.getAllByText('Configure specific Product and Track terms')
                .length
        ).toBeGreaterThan(0);
    });

    it('shows the configure subheading above the cards in custom mode', () => {
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[]}
                termsMode="custom"
                onTermsChange={noop}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        expect(
            screen.getByTestId('configureTermsSubheading')
        ).toHaveTextContent('Configure specific Product and Track terms');
    });

    it('does not render condition rows in label mode', () => {
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[makeTerm()]}
                termsMode="label"
                onTermsChange={noop}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        expect(
            screen.queryByTestId('contractTermConditionRow')
        ).not.toBeInTheDocument();
    });

    it('adds a product term from the card header "+ ADD" button', () => {
        const onTermsChange = jest.fn();
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[]}
                termsMode="custom"
                onTermsChange={onTermsChange}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        fireEvent.click(screen.getByTestId('addProductTerm'));
        expect(onTermsChange).toHaveBeenCalled();
    });

    it('toggles terms mode when the whole radio label is clicked', () => {
        const onTermsModeChange = jest.fn();
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[]}
                termsMode="custom"
                onTermsChange={noop}
                onTermsModeChange={onTermsModeChange}
            />,
            { identity }
        );
        // Click the label text, not the radio circle. The label must be wired
        // to its input so the entire string is a click target.
        fireEvent.click(screen.getByText(/Apply Label Terms/));
        expect(onTermsModeChange).toHaveBeenCalledWith('label');
    });

    it('focuses the new term name field when a product term is added', () => {
        renderInAppContext(<TermsHarness />, { identity });
        fireEvent.click(screen.getByTestId('addProductTerm'));
        expect(screen.getByLabelText('Term Name')).toHaveFocus();
    });

    it('re-opens a collapsed product section when a term is added', () => {
        renderInAppContext(<TermsHarness />, { identity });
        const productCard = screen
            .getByText('Product Terms')
            .closest('.SetupTermsStep-card') as HTMLElement;
        const trigger = productCard.querySelector(
            '.SuiteSection-header-trigger'
        ) as HTMLElement;

        // Collapse the section, then add a term: it must spring back open.
        fireEvent.click(trigger);
        expect(trigger).toHaveAttribute('aria-expanded', 'false');

        fireEvent.click(within(productCard).getByTestId('addProductTerm'));
        expect(trigger).toHaveAttribute('aria-expanded', 'true');
    });

    it('numbers term cards and deletes via the trash icon', () => {
        const onTermsChange = jest.fn();
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[makeTerm({ id: 'a' }), makeTerm({ id: 'b' })]}
                termsMode="custom"
                onTermsChange={onTermsChange}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        expect(screen.getByText('Product Term 1')).toBeInTheDocument();
        expect(screen.getByText('Product Term 2')).toBeInTheDocument();
        fireEvent.click(screen.getByTestId('deleteTerm-a'));
        expect(onTermsChange).toHaveBeenCalled();
    });

    it('styles the term delete button as a round secondary (blue) glyph button', () => {
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[makeTerm({ id: 'a' })]}
                termsMode="custom"
                onTermsChange={noop}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        const btn = screen.getByTestId('deleteTerm-a');
        expect(btn).toHaveClass('btn-secondary');
        expect(btn).not.toHaveClass('btn-ghost');
        // Round icon button, not a rounded rectangle.
        expect(btn).toHaveClass('suite-glyph-button');
    });

    it('shows the product term count in the card header', () => {
        renderInAppContext(
            <SetupTermsStep
                project={project}
                terms={[makeTerm({ id: 'a' }), makeTerm({ id: 'b' })]}
                termsMode="custom"
                onTermsChange={noop}
                onTermsModeChange={noop}
            />,
            { identity }
        );
        expect(screen.getByText('2 Product Terms')).toBeInTheDocument();
    });
});

describe('buildDispatch', () => {
    const cond1 = makeCondition({ priority: 1, termRate: '50' });
    const cond2 = makeCondition({
        priority: 2,
        termRate: '75',
        contractTermConditionId: 'existing-1',
    });
    const term = makeTerm({ conditions: [cond1, cond2] });
    const terms = [term];

    // buildDispatch now passes a functional updater to onTermsChange so that
    // multiple synchronous dispatches (e.g. Copy Conditions) accumulate correctly.
    const apply = (
        fn: jest.Mock,
        initialTerms: TransferTermDraft[]
    ): TransferTermDraft[] => {
        const updater = fn.mock.calls[0][0] as (
            prev: TransferTermDraft[]
        ) => TransferTermDraft[];
        return updater(initialTerms);
    };

    it('ADD_CONTRACT_TERM_CONDITION appends a blank condition with the next priority', () => {
        const onTermsChange = jest.fn();
        buildDispatch(
            'term-1',
            onTermsChange
        )({
            type: 'ADD_CONTRACT_TERM_CONDITION',
        });
        const result = apply(onTermsChange, terms);
        expect(result[0].conditions).toHaveLength(3);
        expect(result[0].conditions[2].priority).toBe(3);
        expect(result[0].conditions[2].contractTermId).toBe('term-1');
        expect(result[0].conditions[2].termRate).toBeNull();
    });

    it('ADD_CONTRACT_TERM_CONDITION uses provided condition data when present', () => {
        const onTermsChange = jest.fn();
        buildDispatch(
            'term-1',
            onTermsChange
        )({
            type: 'ADD_CONTRACT_TERM_CONDITION',
            condition: {
                contractTermConditionName: 'Copied',
                termRate: '60',
                conditions: {
                    countries: ['USA'],
                    stores: [],
                    transactionTypes: [],
                },
                priority: 99,
            },
        });
        const result = apply(onTermsChange, terms);
        expect(result[0].conditions).toHaveLength(3);
        expect(result[0].conditions[2].termRate).toBe('60');
        expect(result[0].conditions[2].conditions.countries).toEqual(['USA']);
        expect(result[0].conditions[2].priority).toBe(3);
    });

    it('DELETE_TERM_CONDITION removes the condition at the given index and re-numbers', () => {
        const onTermsChange = jest.fn();
        buildDispatch(
            'term-1',
            onTermsChange
        )({
            type: 'DELETE_TERM_CONDITION',
            termConditionIndex: 0,
            termCondition: cond1,
        });
        const result = apply(onTermsChange, terms);
        expect(result[0].conditions).toHaveLength(1);
        expect(result[0].conditions[0].priority).toBe(1);
        expect(result[0].conditions[0].termRate).toBe('75');
    });

    it('SET_CONTRACT_TERM_CONDITIONS updates a scalar field on the condition', () => {
        const onTermsChange = jest.fn();
        buildDispatch(
            'term-1',
            onTermsChange
        )({
            type: 'SET_CONTRACT_TERM_CONDITIONS',
            index: 0,
            name: 'termRate',
            value: '80',
        });
        const result = apply(onTermsChange, terms);
        expect(result[0].conditions[0].termRate).toBe('80');
        expect(result[0].conditions[1].termRate).toBe('75');
    });

    it('SET_CONTRACT_TERM_CONDITIONS updates a nested conditions array', () => {
        const onTermsChange = jest.fn();
        buildDispatch(
            'term-1',
            onTermsChange
        )({
            type: 'SET_CONTRACT_TERM_CONDITIONS',
            index: 0,
            name: 'stores',
            value: ['2', '5'],
        });
        const result = apply(onTermsChange, terms);
        expect(result[0].conditions[0].conditions.stores).toEqual(['2', '5']);
        expect(result[0].conditions[1].conditions.stores).toEqual([]);
    });

    it('REORDER_TERM_CONDITIONS moves a condition and re-numbers priorities', () => {
        const onTermsChange = jest.fn();
        buildDispatch(
            'term-1',
            onTermsChange
        )({
            type: 'REORDER_TERM_CONDITIONS',
            condition: cond1,
            dragIndex: 0,
            dropIndex: 1,
        });
        const result = apply(onTermsChange, terms);
        expect(result[0].conditions[0].termRate).toBe('75');
        expect(result[0].conditions[0].priority).toBe(1);
        expect(result[0].conditions[1].termRate).toBe('50');
        expect(result[0].conditions[1].priority).toBe(2);
    });
});
