const MELBOURNE_TIMEZONE = 'Australia/Melbourne';

export type DateRangeFilter = { from: string; to: string };

type MelbourneTimeParts = { hour: number; minute: number; dateKey: string };

export function melbourneTodayKey(): string {
    return new Intl.DateTimeFormat('en-CA', {
        timeZone: MELBOURNE_TIMEZONE,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
    }).format(new Date());
}

export function melbourneNowForPicker(): string {
    const parts = new Intl.DateTimeFormat('en-CA', {
        timeZone: MELBOURNE_TIMEZONE,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false,
    }).formatToParts(new Date());

    const get = (type: Intl.DateTimeFormatPartTypes): string =>
        parts.find((part) => part.type === type)?.value ?? '00';

    const minutes = Number(get('minute'));
    const roundedMinutes = [0, 15, 30, 45].reduce((closest, option) =>
        Math.abs(option - minutes) < Math.abs(closest - minutes) ? option : closest,
    0);

    return `${get('year')}-${get('month')}-${get('day')} ${get('hour')}:${String(roundedMinutes).padStart(2, '0')}`;
}

function melbourneDatePickerKeyFromOption(date: string): string {
    return date.replace(/\//g, '-');
}

export function isMelbourneStartDateSelectable(date: string): boolean {
    return melbourneDatePickerKeyFromOption(date) >= melbourneTodayKey();
}

export function isMelbournePaymentDateSelectable(date: string): boolean {
    return melbourneDatePickerKeyFromOption(date) <= melbourneTodayKey();
}

function melbourneTimePartsNow(): MelbourneTimeParts {
    const parts = new Intl.DateTimeFormat('en-CA', {
        timeZone: MELBOURNE_TIMEZONE,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false,
    }).formatToParts(new Date());

    const get = (type: Intl.DateTimeFormatPartTypes): string =>
        parts.find((part) => part.type === type)?.value ?? '00';

    return {
        dateKey: `${get('year')}-${get('month')}-${get('day')}`,
        hour: Number(get('hour')),
        minute: Number(get('minute')),
    };
}

function minimumMelbourneStartMinute(hour: number, nowParts: MelbourneTimeParts): number {
    if (hour !== nowParts.hour) {
        return hour > nowParts.hour ? 0 : -1;
    }

    const slots = [0, 15, 30, 45];
    const next = slots.find((slot) => slot > nowParts.minute);

    return next ?? 60;
}

export function createMelbourneStartTimeOptions(clientStartDateTime: string | null) {
    return (hour: number, minute: number | null): boolean => {
        if (minute === null) {
            return true;
        }

        if (!clientStartDateTime) {
            return [0, 15, 30, 45].includes(minute);
        }

        const [datePart] = clientStartDateTime.split(' ');
        const today = melbourneTodayKey();

        if (datePart > today) {
            return [0, 15, 30, 45].includes(minute);
        }

        if (datePart < today) {
            return false;
        }

        const nowParts = melbourneTimePartsNow();

        if (hour < nowParts.hour) {
            return false;
        }

        if (hour > nowParts.hour) {
            return [0, 15, 30, 45].includes(minute);
        }

        const minMinute = minimumMelbourneStartMinute(hour, nowParts);

        if (minMinute >= 60) {
            return false;
        }

        return minute >= minMinute && [0, 15, 30, 45].includes(minute);
    };
}

export function ensureMelbourneScheduledDateTimeNotPast(value: string | null): string | null {
    if (!value) {
        return value;
    }

    const today = melbourneTodayKey();
    const [datePart, timePart] = value.split(' ');

    if (!datePart || !timePart) {
        return melbourneNowForPicker();
    }

    if (datePart < today) {
        return melbourneNowForPicker();
    }

    if (datePart === today) {
        const nowParts = melbourneTimePartsNow();
        const [hourStr, minuteStr] = timePart.split(':');
        const hour = Number(hourStr);
        const minute = Number(minuteStr);
        const minMinute = minimumMelbourneStartMinute(hour, nowParts);

        if (hour < nowParts.hour || (hour === nowParts.hour && (minMinute >= 60 || minute < minMinute))) {
            return melbourneNowForPicker();
        }
    }

    return value;
}

export function isCompleteDateRangeFilter(
    value: DateRangeFilter | string | null | undefined,
): boolean {
    if (!value) {
        return false;
    }

    if (typeof value === 'string') {
        return value.trim().length > 0;
    }

    return Boolean(value.from?.trim());
}

export function normalizeDateRangeFilter(
    value: DateRangeFilter | string | null | undefined,
): DateRangeFilter | null {
    if (!value) {
        return null;
    }

    if (typeof value === 'string') {
        const normalized = value.replace(/\//g, '-');

        return { from: normalized, to: normalized };
    }

    const from = value.from?.replace(/\//g, '-');
    const to = value.to?.replace(/\//g, '-');

    if (!from) {
        return null;
    }

    return { from, to: to || from };
}

function formatIsoDateParts(isoDate: string, separator: '-' | '/'): string {
    const [year, month, day] = isoDate.split(/[-/]/);

    if (!year || !month || !day) {
        return isoDate;
    }

    const paddedDay = day.padStart(2, '0');
    const paddedMonth = month.padStart(2, '0');

    return separator === '-'
        ? `${paddedDay}-${paddedMonth}-${year}`
        : `${paddedDay}/${paddedMonth}/${year}`;
}

export function formatAuctionDate(value: string | null | undefined): string {
    if (!value) {
        return '-';
    }

    if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
        return formatIsoDateParts(value, '-');
    }

    const date = new Date(value);

    if (Number.isNaN(date.getTime())) {
        return '-';
    }

    const day = String(date.getDate()).padStart(2, '0');
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const year = date.getFullYear();

    return `${day}-${month}-${year}`;
}

export function formatAuctionDateTime(value: string | null | undefined): string {
    if (!value) {
        return '-';
    }

    const date = new Date(value);

    if (Number.isNaN(date.getTime())) {
        return '-';
    }

    const parts = new Intl.DateTimeFormat('en-GB', {
        timeZone: MELBOURNE_TIMEZONE,
        day: '2-digit',
        month: '2-digit',
        year: 'numeric',
        hour: 'numeric',
        minute: '2-digit',
        hour12: true,
    }).formatToParts(date);

    const get = (type: Intl.DateTimeFormatPartTypes): string =>
        parts.find((part) => part.type === type)?.value ?? '';

    const day = get('day').padStart(2, '0');
    const month = get('month').padStart(2, '0');
    const year = get('year');
    const hour = get('hour').padStart(2, '0');
    const minute = get('minute').padStart(2, '0');
    const dayPeriod = get('dayPeriod').toUpperCase();

    return `${day}-${month}-${year} ${hour}:${minute} ${dayPeriod}`;
}

export function formatAuctionDateRange(
    start: string | null | undefined,
    end: string | null | undefined,
): string {
    if (!start || !end) {
        return '-';
    }

    return `${formatAuctionDateTime(start)} - ${formatAuctionDateTime(end)}`;
}

export function formatAuctionDateRangeDisplay(
    start: string | null | undefined,
    end: string | null | undefined,
): string {
    const formattedStart = start ? formatAuctionDateTime(start) : null;
    const formattedEnd = end ? formatAuctionDateTime(end) : null;

    if (formattedStart && formattedStart !== '-' && formattedEnd && formattedEnd !== '-') {
        return `${formattedStart} - ${formattedEnd}`;
    }

    if (formattedStart && formattedStart !== '-') {
        return formattedStart;
    }

    if (formattedEnd && formattedEnd !== '-') {
        return formattedEnd;
    }

    return '-';
}

export function formatAuctionDateRangeFilter(
    range: DateRangeFilter | string | null | undefined,
): string {
    if (!isCompleteDateRangeFilter(range)) {
        return '';
    }

    const normalized = normalizeDateRangeFilter(range);

    if (!normalized) {
        return '';
    }

    return `${formatAuctionDate(normalized.from)} - ${formatAuctionDate(normalized.to)}`;
}

export function formatMelbourneScheduledDateTime(value: string | null | undefined): string {
    if (!value) {
        return '';
    }

    const [datePart, timePart] = value.split(' ');

    if (!datePart || !timePart) {
        return value;
    }

    const [hour, minute] = timePart.split(':').map(Number);

    if (Number.isNaN(hour) || Number.isNaN(minute)) {
        return value;
    }

    const period = hour >= 12 ? 'PM' : 'AM';
    const hour12 = hour % 12 || 12;

    return `${formatIsoDateParts(datePart, '/')} ${String(hour12).padStart(2, '0')}:${String(minute).padStart(2, '0')} ${period}`;
}

const endedAuctionStatuses = new Set(['sold', 'passed_in', 'not_sold', 'cancelled']);

export function auctionEndDateChipLabel(
    endDateTime: string | null | undefined,
    isLive: boolean,
    status: string,
): string {
    if (!endDateTime) {
        return '';
    }

    const endMs = new Date(endDateTime).getTime();
    const hasEndPassed = !Number.isNaN(endMs) && endMs <= Date.now();
    const hasEnded = hasEndPassed || (!isLive && endedAuctionStatuses.has(status));

    return hasEnded ? 'Ended on' : 'Ends on';
}

export function isAuctionEndStillInFuture(endDateTime: string | null | undefined): boolean {
    if (!endDateTime) {
        return false;
    }

    const endMs = new Date(endDateTime).getTime();

    return !Number.isNaN(endMs) && endMs > Date.now();
}

/**
 * Days until payment due (end of due date). Negative when overdue.
 */
export function paymentDueDaysRemaining(paymentDueDate: string | null | undefined): number | null {
    if (!paymentDueDate) {
        return null;
    }

    const endTime = new Date(`${paymentDueDate}T23:59:59`);
    const diffMs = endTime.getTime() - Date.now();

    return Math.floor(diffMs / (1000 * 60 * 60 * 24));
}

export function isPaymentDueOverdue(daysRemaining: number): boolean {
    return daysRemaining < 0;
}

export function formatPaymentDueDaysLabel(daysRemaining: number): string {
    const unit = Math.abs(daysRemaining) === 1 ? 'day' : 'days';

    return `${daysRemaining} ${unit}`;
}
