export const CONFLICT_CHECK_STATE = Object.freeze({
    COMPLETE: 'COMPLETE',
    PARTIAL: 'PARTIAL',
    NOT_RUN: 'NOT_RUN'
});

export const CONFLICT_CHECK_REASON = Object.freeze({
    NO_DEPARTMENTS: 'NO_DEPARTMENTS',
    ALL_REQUESTS_FAILED: 'ALL_REQUESTS_FAILED'
});

/** Формирует date-only строку из локальных компонентов без UTC-конвертации. */
export function formatLocalDate(date) {
    if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
        throw new TypeError('Для форматирования требуется корректная дата');
    }
    return [
        String(date.getFullYear()).padStart(4, '0'),
        String(date.getMonth() + 1).padStart(2, '0'),
        String(date.getDate()).padStart(2, '0')
    ].join('-');
}

/** Возвращает локальные границы недели с понедельника по воскресенье включительно. */
export function currentWeekDateRange(referenceDate = new Date()) {
    if (!(referenceDate instanceof Date) || Number.isNaN(referenceDate.getTime())) {
        throw new TypeError('Для расчёта недели требуется корректная дата');
    }

    const localDate = new Date(
        referenceDate.getFullYear(),
        referenceDate.getMonth(),
        referenceDate.getDate()
    );
    const dayOfWeek = localDate.getDay();
    const daysFromMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
    const monday = addLocalDays(localDate, daysFromMonday);
    const sunday = addLocalDays(monday, 6);

    return {
        startDate: formatLocalDate(monday),
        endDate: formatLocalDate(sunday)
    };
}

/**
 * Загружает расписания независимо и сохраняет факт каждого отказа.
 * Технические причины намеренно не возвращаются в UI-модель.
 */
export async function loadDepartmentSchedules(departments, loadSchedule) {
    if (!Array.isArray(departments)) {
        throw new TypeError('Список кафедр должен быть массивом');
    }
    if (typeof loadSchedule !== 'function') {
        throw new TypeError('Функция загрузки расписания обязательна');
    }

    if (departments.length === 0) {
        return {
            state: CONFLICT_CHECK_STATE.NOT_RUN,
            reason: CONFLICT_CHECK_REASON.NO_DEPARTMENTS,
            lessons: [],
            totalDepartmentCount: 0,
            checkedDepartmentCount: 0,
            failedDepartments: []
        };
    }

    const results = await Promise.allSettled(departments.map(async department => {
        const lessons = await loadSchedule(department);
        if (!Array.isArray(lessons)) {
            throw new TypeError('API расписания вернул некорректный формат данных');
        }
        return lessons;
    }));

    const lessons = [];
    const failedDepartments = [];
    let checkedDepartmentCount = 0;

    results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
            checkedDepartmentCount += 1;
            lessons.push(...result.value);
            return;
        }
        failedDepartments.push(publicDepartment(departments[index], index));
    });

    const state = checkedDepartmentCount === 0
        ? CONFLICT_CHECK_STATE.NOT_RUN
        : failedDepartments.length > 0
            ? CONFLICT_CHECK_STATE.PARTIAL
            : CONFLICT_CHECK_STATE.COMPLETE;

    return {
        state,
        reason: state === CONFLICT_CHECK_STATE.NOT_RUN
            ? CONFLICT_CHECK_REASON.ALL_REQUESTS_FAILED
            : null,
        lessons,
        totalDepartmentCount: departments.length,
        checkedDepartmentCount,
        failedDepartments
    };
}

/** Возвращает безопасную UI-модель итогового состояния проверки. */
export function conflictCheckPresentation(scheduleLoad, conflictCount, range) {
    assertPresentationInput(scheduleLoad, conflictCount, range);

    const count = conflictCount;
    const startDate = range.startDate;
    const endDate = range.endDate;
    const rangeLabel = `${startDate} — ${endDate}`;

    if (scheduleLoad?.state === CONFLICT_CHECK_STATE.NOT_RUN) {
        const noDepartments = scheduleLoad.reason === CONFLICT_CHECK_REASON.NO_DEPARTMENTS;
        return {
            tone: 'error',
            title: 'Проверка не выполнена',
            description: noDepartments
                ? 'Нет кафедр, расписание которых можно проверить.'
                : 'Не удалось получить расписание ни одной кафедры. Подтвердить отсутствие конфликтов нельзя.',
            meta: `Период: ${rangeLabel}`
        };
    }

    if (scheduleLoad?.state === CONFLICT_CHECK_STATE.PARTIAL) {
        return {
            tone: 'warning',
            title: 'Проверка выполнена частично',
            description: count > 0
                ? 'Показаны найденные конфликты, но подтвердить полноту результата нельзя.'
                : 'В доступной части расписания конфликтов не найдено, но подтвердить их отсутствие во всём расписании нельзя.',
            meta: `Проверено кафедр: ${scheduleLoad.checkedDepartmentCount} из `
                + `${scheduleLoad.totalDepartmentCount} · Период: ${rangeLabel}`
        };
    }

    if (scheduleLoad?.state === CONFLICT_CHECK_STATE.COMPLETE && count === 0) {
        return {
            tone: 'success',
            title: 'Конфликты расписания не обнаружены',
            description: 'Проверены накладки преподавателей и аудиторий, а также вместимость помещений.',
            meta: `Проверено кафедр: ${scheduleLoad.checkedDepartmentCount} из `
                + `${scheduleLoad.totalDepartmentCount} · Период: ${rangeLabel}`
        };
    }

    return null;
}

function addLocalDays(date, days) {
    const result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
}

function assertPresentationInput(scheduleLoad, conflictCount, range) {
    if (!Number.isInteger(conflictCount) || conflictCount < 0) {
        throw new TypeError('Количество конфликтов должно быть целым неотрицательным числом');
    }

    if (!isValidDateOnly(range?.startDate) || !isValidDateOnly(range?.endDate)
        || range.startDate > range.endDate) {
        throw new TypeError('Для результата проверки требуется корректный локальный диапазон дат');
    }

    const total = scheduleLoad?.totalDepartmentCount;
    const checked = scheduleLoad?.checkedDepartmentCount;
    if (!Number.isInteger(total) || total < 0
        || !Number.isInteger(checked) || checked < 0 || checked > total) {
        throw new TypeError('Количество проверенных кафедр не соответствует результату проверки');
    }

    if (scheduleLoad.state === CONFLICT_CHECK_STATE.COMPLETE) {
        if (total === 0 || checked !== total) {
            throw new TypeError('Полная проверка должна охватывать все кафедры');
        }
        return;
    }

    if (scheduleLoad.state === CONFLICT_CHECK_STATE.PARTIAL) {
        if (checked === 0 || checked === total) {
            throw new TypeError('Частичная проверка должна охватывать только часть кафедр');
        }
        return;
    }

    if (scheduleLoad.state === CONFLICT_CHECK_STATE.NOT_RUN && checked === 0) {
        return;
    }

    throw new TypeError('Неизвестное состояние проверки конфликтов');
}

function isValidDateOnly(value) {
    const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value || '');
    if (!match) return false;

    const [, year, month, day] = match.map(Number);
    const date = new Date(year, month - 1, day);
    return date.getFullYear() === year
        && date.getMonth() === month - 1
        && date.getDate() === day;
}

function publicDepartment(department, index) {
    return {
        id: department?.id ?? null,
        name: department?.departmentName || department?.name || `Кафедра ${index + 1}`
    };
}
