import { ApolloClient } from '@apollo/client';
import { getContract } from 'src/apollo/queries/contract';
import {
    BulkEditEntryBase,
    BulkSchema,
    runInBatches,
} from './common-bulk-edit-schema';
import type { GridTableColumnDefinition } from '@theorchard/suite-components';
import React from 'react';
import { Link } from 'react-router-dom';
import { getContractDetail } from 'src/urls/frontend-royalties';
import updateContractLifecycleSchedules from 'src/apollo/mutations/contract/update-contract-lifecycle-schedules.gql';
import updateContractLifecycleTermStart from 'src/apollo/mutations/contract/update-contract-lifecycle-term-start.gql';
import {
    normalizeEnum,
    convertXlsxCellToDateString,
    PERIOD_TYPES,
    RENEWAL_TYPES,
} from './utils';
import { GetContractQuery } from 'src/apollo/queries/contract/__generated__/get-contract';
import { CellProps } from '@theorchard/suite-components/dist/esm/src/components/table/base/types';
import {
    GET_CONTRACT_MAX_PARALLEL,
    UPDATE_CONTRACT_LIFECYCLE_MAX_PARALLEL,
} from 'src/components/contract-bulk-edit/common/edit-schema/constants';

const HEADERS: GridTableColumnDefinition<ContractLifecycleBulkEditEntry>[] = [
    {
        name: 'contractId',
        title: 'Contract Id',
    },
    {
        name: 'contractName',
        title: 'Contract Name',
        Cell: ({
            data: { contractId, contractName },
        }: CellProps<ContractLifecycleBulkEditEntry>) => (
            <div>
                {contractName ? (
                    <Link key={contractId} to={getContractDetail(contractId)}>
                        {contractName}
                    </Link>
                ) : (
                    '-'
                )}
            </div>
        ),
    },
    {
        name: 'currentStartDate',
        title: 'Current Start Date',
    },
    {
        name: 'currentLifecycleSummary',
        title: 'Current Lifecycle Summary',
        Cell: ({ data }: CellProps<ContractLifecycleBulkEditEntry>) => (
            <div style={{ whiteSpace: 'pre-line' }}>
                {data.currentLifecycleSummary}
            </div>
        ),
    },
    {
        name: 'lifecycleSummaryToSet',
        title: 'Lifecycle Summary to Set',
        Cell: ({ data }: CellProps<ContractLifecycleBulkEditEntry>) => (
            <div style={{ whiteSpace: 'pre-line' }}>
                {data.lifecycleSummaryToSet}
            </div>
        ),
    },
];

type LifecycleSchedule = NonNullable<
    NonNullable<GetContractQuery['abacusContract']>['lifecycleSchedules']
>[number];

export interface ContractLifecycleBulkEditEntry extends BulkEditEntryBase {
    id: string;
    contractId: string;
    contractName: string | undefined | null;

    currentStartDate: string | undefined | null;
    currentLifecycleSummary: string | undefined | null;

    startDateToSet: string | undefined | null;
    lifecycleSummaryToSet: string | undefined | null;

    currentLifecycleSchedules: LifecycleSchedule[] | undefined | null;
    // normalized input to apply
    lifecycleSchedulesToSet: LifecycleSchedule[] | undefined | null;
}

const SCHEDULE_SEPARATOR = ' \n ';

async function updateContractStartDate(
    apolloClient: ApolloClient<object>,
    entry: ContractLifecycleBulkEditEntry
) {
    await apolloClient.mutate({
        mutation: updateContractLifecycleTermStart,
        variables: {
            contractId: entry.contractId,
            lifecycle: {
                lifecycleTermStart: entry.startDateToSet,
            },
        },
    });
}

async function updateLifecycleSchedules(
    apolloClient: ApolloClient<object>,
    entry: ContractLifecycleBulkEditEntry
) {
    if (!entry.lifecycleSchedulesToSet) {
        return;
    }
    const lifecycleSchedulesToUpdate = entry.lifecycleSchedulesToSet.map(
        (schedule, index) => {
            let existingSchedule = null;

            if (
                entry.currentLifecycleSchedules &&
                entry.currentLifecycleSchedules.length > index
            ) {
                existingSchedule = entry.currentLifecycleSchedules[index];
            }
            return {
                contractLifecycleScheduleId:
                    existingSchedule?.contractLifecycleScheduleId,
                // contractLifecycle: existingSchedule.,
                renewalOffsetDetailInterval:
                    schedule.renewalOffsetDetail?.periodInterval,
                renewalOffsetDetailType:
                    schedule.renewalOffsetDetail?.periodType,
                renewalType: schedule.renewalType,
                scheduleEnd: schedule.scheduleEnd,
                terminationNoticeDetailInterval:
                    schedule.terminationNoticeDetail?.periodInterval ||
                    existingSchedule?.terminationNoticeDetail?.periodInterval,
                terminationNoticeDetailType:
                    schedule.terminationNoticeDetail?.periodType ||
                    existingSchedule?.terminationNoticeDetail?.periodType,
            };
        }
    );
    await apolloClient.mutate({
        mutation: updateContractLifecycleSchedules,
        variables: {
            contractId: entry.contractId,
            allowEndDateInPast: true,
            lifecycleSchedules: lifecycleSchedulesToUpdate,
        },
    });
}

export const contractLifecycleSchema: BulkSchema<ContractLifecycleBulkEditEntry> =
    {
        id: 'contractLifecycle',
        displayName: 'Contract Lifecycle',
        columns: HEADERS,
        expectedFileColumns: [
            { name: 'contract_id', description: 'Contract ID to update' },
            {
                name: 'lifecycle_term_start',
                description:
                    'Date (YYYY-MM-DD). Optional. Sets lifecycleTermStart (aka initial period start date).',
            },
            {
                name: 'initial_schedule_end',
                description:
                    'Date (YYYY-MM-DD). Optional. First schedule end date.',
            },
            {
                name: 'initial_renewal_type',
                description:
                    'Renewal type for first schedule. One of CONTINUOUSLY_ACTIVE, RENEW_AFTER_CERTAIN_DATE, RENEW_PERIODICALLY (must match GraphQL enum).',
            },
            {
                name: 'initial_renewal_offset_interval',
                description:
                    'Number. Renewal offset period interval for first schedule.',
            },
            {
                name: 'initial_renewal_offset_type',
                description:
                    'Period type for renewal offset (e.g. DAY, MONTH, YEAR).',
            },
            {
                name: 'initial_termination_notice_interval',
                description:
                    'Number. Termination notice interval for first schedule.',
            },
            {
                name: 'initial_termination_notice_type',
                description:
                    'Period type for termination notice (e.g. DAY, MONTH, YEAR).',
            },
            {
                name: 'subsequent_renewal_type',
                description:
                    'Optional. Renewal type for the second schedule. If omitted, only one schedule is updated.',
            },
            {
                name: 'subsequent_renewal_offset_interval',
                description:
                    'Number. Renewal offset period interval for the second schedule.',
            },
            {
                name: 'subsequent_renewal_offset_type',
                description:
                    'Period type for the second schedule renewal offset.',
            },
            {
                name: 'subsequent_termination_notice_interval',
                description:
                    'Number. Termination notice interval for the second schedule.',
            },
            {
                name: 'subsequent_termination_notice_type',
                description:
                    'Period type for the second schedule termination notice.',
            },
        ],
        async toEntries(apolloClient: ApolloClient<object>, rows: any[]) {
            function summarizeSchedule(schedule: LifecycleSchedule): string {
                if (!schedule) return '-';
                const parts: string[] = [];
                if (schedule.scheduleEnd)
                    parts.push(`end: ${schedule.scheduleEnd}`);
                if (schedule.renewalType)
                    parts.push(`renewal: ${schedule.renewalType}`);
                if (schedule.renewalOffsetDetail) {
                    const r = schedule.renewalOffsetDetail;
                    if (r.periodInterval && r.periodType)
                        parts.push(
                            `renewalOffset: ${r.periodInterval} ${r.periodType}`
                        );
                }
                if (schedule.terminationNoticeDetail) {
                    const t = schedule.terminationNoticeDetail;
                    if (t.periodInterval && t.periodType)
                        parts.push(
                            `terminationNotice: ${t.periodInterval} ${t.periodType}`
                        );
                }
                return parts.join(', ');
            }

            function buildSchedulesFromRow(
                row: any
            ): LifecycleSchedule[] | undefined {
                const first: any = {};
                const initialEnd = convertXlsxCellToDateString(
                    row['initial_schedule_end']
                );
                const initialRenewalType = normalizeEnum(
                    row['initial_renewal_type'],
                    RENEWAL_TYPES
                );
                const initialRenewalOffsetInterval =
                    row['initial_renewal_offset_interval'];
                const initialRenewalOffsetType = normalizeEnum(
                    row['initial_renewal_offset_type'],
                    PERIOD_TYPES
                );
                const initialTermNoticeInterval =
                    row['initial_termination_notice_interval'];
                const initialTermNoticeType = normalizeEnum(
                    row['initial_termination_notice_type'],
                    PERIOD_TYPES
                );

                const hasFirst = initialRenewalType;

                const second: any = {};
                const secondRenewalType = normalizeEnum(
                    row['subsequent_renewal_type'],
                    RENEWAL_TYPES
                );
                const secondRenewalOffsetInterval =
                    row['subsequent_renewal_offset_interval'];
                const secondRenewalOffsetType = normalizeEnum(
                    row['subsequent_renewal_offset_type'],
                    PERIOD_TYPES
                );
                const secondTermNoticeInterval =
                    row['subsequent_termination_notice_interval'];
                const secondTermNoticeType = normalizeEnum(
                    row['subsequent_termination_notice_type'],
                    PERIOD_TYPES
                );

                const hasSecond = secondRenewalType;

                const result: any[] = [];

                if (hasFirst) {
                    if (initialEnd) first.scheduleEnd = initialEnd;
                    if (initialRenewalType)
                        first.renewalType = initialRenewalType;
                    if (
                        initialRenewalOffsetInterval ||
                        initialRenewalOffsetType
                    ) {
                        first.renewalOffsetDetail = {
                            periodInterval: initialRenewalOffsetInterval
                                ? Number(initialRenewalOffsetInterval)
                                : undefined,
                            periodType: initialRenewalOffsetType || undefined,
                        };
                    }
                    if (initialTermNoticeInterval || initialTermNoticeType) {
                        first.terminationNoticeDetail = {
                            periodInterval: initialTermNoticeInterval
                                ? Number(initialTermNoticeInterval)
                                : undefined,
                            periodType: initialTermNoticeType || undefined,
                        };
                    }
                    result.push(first);
                }

                if (hasSecond) {
                    if (secondRenewalType)
                        second.renewalType = secondRenewalType;
                    if (
                        secondRenewalOffsetInterval ||
                        secondRenewalOffsetType
                    ) {
                        second.renewalOffsetDetail = {
                            periodInterval: secondRenewalOffsetInterval
                                ? Number(secondRenewalOffsetInterval)
                                : undefined,
                            periodType: secondRenewalOffsetType || undefined,
                        };
                    }
                    if (secondTermNoticeInterval || secondTermNoticeType) {
                        second.terminationNoticeDetail = {
                            periodInterval: secondTermNoticeInterval
                                ? Number(secondTermNoticeInterval)
                                : undefined,
                            periodType: secondTermNoticeType || undefined,
                        };
                    }
                    result.push(second);
                }

                return result.length ? result : undefined;
            }

            async function mapJsonToEditEntries(
                parsedJsonArray: any[]
            ): Promise<ContractLifecycleBulkEditEntry[]> {
                const mapEntry = async (
                    row: any
                ): Promise<ContractLifecycleBulkEditEntry> => {
                    const contractId: string = row['contract_id'];
                    const lifecycleTermStartToSet: string | null =
                        convertXlsxCellToDateString(
                            row['lifecycle_term_start']
                        );

                    const { data: contractData } = await apolloClient.query({
                        query: getContract,
                        variables: {
                            contractId,
                            groupAdmin: 'PRESENTATIONAL',
                        },
                        fetchPolicy: 'network-only',
                    });

                    const contract = contractData?.abacusContract;
                    if (!contract) {
                        return {
                            id: contractId,
                            contractId,
                            contractName: undefined,
                            currentStartDate: undefined,
                            currentLifecycleSummary: undefined,
                            startDateToSet:
                                lifecycleTermStartToSet || undefined,
                            lifecycleSummaryToSet: undefined,
                            currentLifecycleSchedules: [],
                            lifecycleSchedulesToSet: buildSchedulesFromRow(row),
                        };
                    }

                    const lifecycle = contract.lifecycle;
                    const lifecycleSchedules =
                        contract.lifecycleSchedules || [];
                    const currentStartDate =
                        lifecycle?.lifecycleTermStart || null;
                    const currentLifecycleSummary = lifecycleSchedules
                        .map(
                            (s: any, idx: number) =>
                                `${idx === 0 ? 'Current Period' : 'Subsequent Period'}: ${summarizeSchedule(s)}`
                        )
                        .join(SCHEDULE_SEPARATOR);

                    const schedulesToSet = buildSchedulesFromRow(row);
                    const lifecycleSummaryToSet = schedulesToSet
                        ? schedulesToSet
                              .map(
                                  (s: any, idx: number) =>
                                      `${idx === 0 ? 'Current Period' : 'Subsequent Period'}: ${summarizeSchedule(s)}`
                              )
                              .join(SCHEDULE_SEPARATOR)
                        : undefined;

                    return {
                        id: contractId,
                        contractId,
                        contractName: contract.contractName,
                        currentStartDate,
                        currentLifecycleSummary,
                        startDateToSet: lifecycleTermStartToSet || undefined,
                        lifecycleSummaryToSet,
                        currentLifecycleSchedules: lifecycleSchedules,
                        lifecycleSchedulesToSet: schedulesToSet,
                    };
                };

                return await runInBatches(
                    parsedJsonArray,
                    GET_CONTRACT_MAX_PARALLEL,
                    mapEntry
                );
            }

            return await mapJsonToEditEntries(rows);
        },
        async apply(apolloClient, entries, onSuccess, onFail) {
            await runInBatches(
                entries,
                UPDATE_CONTRACT_LIFECYCLE_MAX_PARALLEL,
                async entry => {
                    if (!entry.contractId) {
                        onFail(
                            entry,
                            new Error('Missing contract id'),
                            'Missing contract id'
                        );
                        return;
                    }

                    if (!entry.contractName) {
                        onFail(
                            entry,
                            new Error('No contract to update.'),
                            'No contract to update.'
                        );
                        return;
                    }

                    try {
                        // Update lifecycle term start if provided
                        if (entry.startDateToSet) {
                            await updateContractStartDate(apolloClient, entry);
                        }

                        // Update lifecycle schedules if provided
                        if (
                            entry.lifecycleSchedulesToSet &&
                            entry.lifecycleSchedulesToSet.length
                        ) {
                            await updateLifecycleSchedules(apolloClient, entry);
                        }

                        onSuccess(entry);
                    } catch (error) {
                        onFail(
                            entry,
                            error,
                            'Error updating contract lifecycle. ' + error
                        );
                    }
                }
            );
        },
    };
