import { api } from '../api.js';
import { escapeHtml, hideAlert, showAlert } from '../utils.js';

const EDIT_ROLES = new Set(['ADMIN', 'EDUCATION_OFFICE']);
const ACTION_LABELS = {
    MOVE: 'Перенос',
    REPLACE: 'Замена',
    CANCEL: 'Отмена'
};
const DAY_LABELS = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
const MONTH_LABELS = [
    'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
    'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
];

export function canEditScheduleOverrides(role) {
    return EDIT_ROLES.has(role);
}

export function overrideActionForChanges(changes) {
    if (changes.dateChanged || changes.timeChanged) return 'MOVE';
    if (changes.teacherChanged || changes.classroomChanged || changes.formatChanged) return 'REPLACE';
    return null;
}

export function weekDates(value) {
    const selected = parseIsoDate(value);
    const day = selected.getDay() || 7;
    const monday = addDays(selected, 1 - day);
    return Array.from({ length: 7 }, (_, index) => toIsoDate(addDays(monday, index)));
}

export function pickMatchingTimeSlot(slots, preferredId, startTime, endTime) {
    const safeSlots = Array.isArray(slots) ? slots : [];
    const byId = preferredId == null
        ? null
        : safeSlots.find(slot => String(slot.id) === String(preferredId));
    if (byId) return byId;
    const normalizedStart = trimTime(startTime);
    const normalizedEnd = trimTime(endTime);
    if (!normalizedStart || !normalizedEnd) return null;
    return safeSlots.find(slot => trimTime(slot.startTime) === normalizedStart
        && trimTime(slot.endTime) === normalizedEnd) || null;
}

export function createScheduleOverridePanel({ role, dictionaries, getRange, onChanged }) {
    if (!canEditScheduleOverrides(role)) {
        return {
            setRegistry() {},
            openLesson() {},
            openRegistry() {}
        };
    }

    const drawer = document.getElementById('schedule-override-drawer');
    const form = document.getElementById('schedule-override-form');
    const empty = document.getElementById('schedule-override-empty');
    const registryButton = document.getElementById('schedule-view-overrides-open');
    const registryCount = document.getElementById('schedule-view-overrides-count');
    const registry = document.getElementById('schedule-override-registry');
    const registryPeriod = document.getElementById('schedule-override-registry-period');
    const calendar = document.getElementById('schedule-override-calendar');
    const calendarToggle = document.getElementById('schedule-override-calendar-toggle');
    const week = document.getElementById('schedule-override-week');
    const timeSelect = document.getElementById('schedule-override-time');
    const teacherSelect = document.getElementById('schedule-override-teacher');
    const classroomSelect = document.getElementById('schedule-override-classroom');
    const formatSelect = document.getElementById('schedule-override-format');
    const commentInput = document.getElementById('schedule-override-comment');
    const restoreButton = document.getElementById('schedule-override-restore');
    const cancelButton = document.getElementById('schedule-override-cancel');
    const saveButton = document.getElementById('schedule-override-save');

    const state = {
        overrides: [],
        selectedLesson: null,
        selectedOverride: null,
        source: null,
        availability: null,
        targetDate: null,
        timeSlots: [],
        busy: false
    };

    registryButton.hidden = false;
    fillReferenceSelects();
    bindEvents();
    renderRegistry();

    return { setRegistry, openLesson, openRegistry };

    function bindEvents() {
        drawer.querySelectorAll('[data-override-close]').forEach(button => {
            button.addEventListener('click', close);
        });
        drawer.querySelectorAll('[data-override-tab]').forEach(button => {
            button.addEventListener('click', () => activateTab(button.dataset.overrideTab));
        });
        registryButton.addEventListener('click', openRegistry);
        calendarToggle.addEventListener('click', () => {
            calendar.hidden = !calendar.hidden;
            calendarToggle.textContent = calendar.hidden ? 'Выбрать другую дату' : 'Скрыть календарь семестра';
        });
        week.addEventListener('click', selectDateFromButton);
        calendar.addEventListener('click', selectDateFromButton);
        registry.addEventListener('click', event => {
            const button = event.target.closest('[data-override-registry-id]');
            if (!button) return;
            const item = state.overrides.find(override => String(override.id) === button.dataset.overrideRegistryId);
            if (item) openOverride(item);
        });
        [timeSelect, teacherSelect, classroomSelect, formatSelect].forEach(select => {
            select.addEventListener('change', updateComparison);
        });
        form.addEventListener('submit', save);
        cancelButton.addEventListener('click', cancelLesson);
        restoreButton.addEventListener('click', restoreRule);
        document.addEventListener('keydown', event => {
            if (event.key === 'Escape' && !drawer.hidden) close();
        });
    }

    function setRegistry(overrides, range) {
        state.overrides = Array.isArray(overrides) ? overrides : [];
        registryCount.textContent = String(state.overrides.length);
        const activeRange = range || getRange?.();
        registryPeriod.textContent = activeRange
            ? `${formatDate(activeRange.startDate)} — ${formatDate(activeRange.endDate)}`
            : 'Период не выбран';
        renderRegistry();
    }

    async function openLesson(lesson) {
        if (!lesson?.scheduleRuleSlotId) return;
        const originalDate = lesson.originalLessonDate || lesson.date;
        const existing = state.overrides.find(override =>
            String(override.baseRuleSlotId) === String(lesson.scheduleRuleSlotId)
            && override.lessonDate === originalDate
        ) || null;
        await prepareEditor(lesson, existing);
    }

    async function openOverride(override) {
        const syntheticLesson = {
            scheduleRuleSlotId: override.baseRuleSlotId,
            date: override.targetLessonDate || override.lessonDate,
            originalLessonDate: override.lessonDate,
            scheduleOverrideId: override.id,
            subjectName: override.subjectName,
            lessonTypeName: override.lessonTypeName,
            groupNames: override.groupNames,
            timeSlotId: override.newTimeSlotId || override.sourceTimeSlotId,
            timeSlotOrder: override.sourceTimeSlotOrder,
            startTime: override.sourceStartTime,
            endTime: override.sourceEndTime,
            teacherId: override.newTeacherId || override.sourceTeacherId,
            teacherName: teacherName(override.newTeacherId) || override.sourceTeacherName,
            classroomId: override.newClassroomId || override.sourceClassroomId,
            classroomName: classroomName(override.newClassroomId) || override.sourceClassroomName,
            lessonFormat: override.newLessonFormat || override.sourceLessonFormat
        };
        await prepareEditor(syntheticLesson, override);
    }

    function openRegistry() {
        open();
        activateTab('registry');
        renderRegistry();
    }

    async function prepareEditor(lesson, existing) {
        open();
        activateTab('edit');
        form.hidden = true;
        empty.hidden = false;
        empty.textContent = 'Загрузка доступных дат и времени...';
        hideAlert('schedule-override-alert');

        state.selectedLesson = lesson;
        state.selectedOverride = existing;
        state.source = sourceFrom(lesson, existing);
        state.targetDate = existing?.targetLessonDate || state.source.lessonDate;

        try {
            state.availability = await api.get(
                `/api/edu-office/schedule/overrides/availability?baseRuleSlotId=${encodeURIComponent(state.source.baseRuleSlotId)}`
                + `&lessonDate=${encodeURIComponent(state.source.lessonDate)}`
            );
            renderDateChoices();
            await loadTimeSlots(initialTimePreference(existing));
            setEditorValues(existing);
            renderSource();
            updateComparison();
            restoreButton.hidden = !existing;
            form.hidden = false;
            empty.hidden = true;
        } catch (error) {
            empty.textContent = error.message || 'Не удалось загрузить данные занятия';
        }
    }

    function sourceFrom(lesson, existing) {
        return {
            baseRuleSlotId: lesson.scheduleRuleSlotId,
            lessonDate: lesson.originalLessonDate || existing?.lessonDate || lesson.date,
            subjectName: existing?.subjectName || lesson.subjectName || 'Без дисциплины',
            lessonTypeName: existing?.lessonTypeName || lesson.lessonTypeName || 'Тип не указан',
            groupNames: existing?.groupNames?.length ? existing.groupNames : (lesson.groupNames || []),
            timeSlotId: existing?.sourceTimeSlotId || lesson.timeSlotId,
            timeSlotOrder: existing?.sourceTimeSlotOrder || lesson.timeSlotOrder,
            startTime: existing?.sourceStartTime || lesson.startTime,
            endTime: existing?.sourceEndTime || lesson.endTime,
            teacherId: existing?.sourceTeacherId || lesson.teacherId,
            teacherName: existing?.sourceTeacherName || lesson.teacherName,
            classroomId: existing?.sourceClassroomId || lesson.classroomId,
            classroomName: existing?.sourceClassroomName || lesson.classroomName,
            lessonFormat: existing?.sourceLessonFormat || lesson.lessonFormat || 'Очно'
        };
    }

    function initialTimePreference(existing) {
        return {
            id: existing?.newTimeSlotId || state.source.timeSlotId,
            startTime: existing?.newTimeSlotId ? null : state.source.startTime,
            endTime: existing?.newTimeSlotId ? null : state.source.endTime
        };
    }

    function fillReferenceSelects() {
        teacherSelect.innerHTML = (dictionaries.teachers || []).map(teacher =>
            `<option value="${escapeHtml(teacher.id)}">${escapeHtml(teacher.fullName || teacher.username || `Преподаватель ${teacher.id}`)}</option>`
        ).join('');
        classroomSelect.innerHTML = (dictionaries.classrooms || []).map(classroom =>
            `<option value="${escapeHtml(classroom.id)}">${escapeHtml(classroomOptionLabel(classroom))}</option>`
        ).join('');
    }

    function setEditorValues(existing) {
        const effectiveTeacherId = existing?.newTeacherId || state.source.teacherId || '';
        const effectiveClassroomId = existing?.newClassroomId || state.source.classroomId || '';
        ensureSelectOption(
            teacherSelect,
            effectiveTeacherId,
            teacherName(effectiveTeacherId) || state.source.teacherName || `Преподаватель ${effectiveTeacherId}`
        );
        ensureSelectOption(
            classroomSelect,
            effectiveClassroomId,
            classroomName(effectiveClassroomId) || state.source.classroomName || `Аудитория ${effectiveClassroomId}`
        );
        teacherSelect.value = String(effectiveTeacherId);
        classroomSelect.value = String(effectiveClassroomId);
        formatSelect.value = existing?.newLessonFormat || state.source.lessonFormat || 'Очно';
        commentInput.value = existing?.comment || '';
    }

    async function selectDateFromButton(event) {
        const button = event.target.closest('[data-override-date]');
        if (!button || button.disabled || button.dataset.overrideDate === state.targetDate) return;
        state.targetDate = button.dataset.overrideDate;
        renderDateChoices();
        await loadTimeSlots({
            id: state.source.timeSlotId,
            startTime: state.source.startTime,
            endTime: state.source.endTime
        });
        updateComparison();
    }

    function renderDateChoices() {
        const available = new Set(state.availability?.availableDates || []);
        week.innerHTML = weekDates(state.source.lessonDate).map((date, index) => dateButton(date, DAY_LABELS[index], available)).join('');
        calendar.innerHTML = renderSemesterCalendar(
            state.availability.semesterStartDate,
            state.availability.semesterEndDate,
            available,
            state.targetDate
        );
    }

    function dateButton(date, label, available) {
        const selected = date === state.targetDate ? ' active' : '';
        const disabled = available.has(date) ? '' : ' disabled';
        return `
            <button type="button" class="schedule-override-date${selected}" data-override-date="${escapeHtml(date)}"${disabled}>
                <span>${escapeHtml(label)}</span>
                <strong>${escapeHtml(formatDateShort(date))}</strong>
            </button>
        `;
    }

    async function loadTimeSlots(preference) {
        timeSelect.disabled = true;
        timeSelect.innerHTML = '<option value="">Загрузка...</option>';
        state.timeSlots = await api.get(
            `/api/admin/time-slots/effective?date=${encodeURIComponent(state.targetDate)}`
        );
        const selected = pickMatchingTimeSlot(
            state.timeSlots,
            preference?.id,
            preference?.startTime,
            preference?.endTime
        );
        timeSelect.innerHTML = '<option value="">Выберите время</option>' + state.timeSlots.map(slot =>
            `<option value="${escapeHtml(slot.id)}">${escapeHtml(timeSlotLabel(slot))}</option>`
        ).join('');
        timeSelect.value = selected ? String(selected.id) : '';
        timeSelect.disabled = false;
    }

    function renderSource() {
        document.getElementById('schedule-override-before-title').textContent =
            `${state.source.subjectName} · ${state.source.lessonTypeName}`;
        document.getElementById('schedule-override-before-meta').textContent = [
            formatDate(state.source.lessonDate),
            intervalLabel(state.source.startTime, state.source.endTime),
            state.source.teacherName,
            state.source.classroomName,
            state.source.lessonFormat,
            state.source.groupNames.join(', ')
        ].filter(Boolean).join(' · ');
    }

    function updateComparison() {
        if (!state.source) return;
        const selectedTime = state.timeSlots.find(slot => String(slot.id) === timeSelect.value);
        document.getElementById('schedule-override-after-title').textContent =
            `${state.source.subjectName} · ${state.source.lessonTypeName}`;
        document.getElementById('schedule-override-after-meta').textContent = [
            formatDate(state.targetDate),
            selectedTime ? intervalLabel(selectedTime.startTime, selectedTime.endTime) : 'Время не выбрано',
            teacherName(teacherSelect.value),
            classroomName(classroomSelect.value),
            formatSelect.value,
            state.source.groupNames.join(', ')
        ].filter(Boolean).join(' · ');
    }

    async function save(event) {
        event.preventDefault();
        if (state.busy) return;
        hideAlert('schedule-override-alert');
        const payload = buildPayload();
        if (!payload) return;
        await mutate(async () => {
            if (state.selectedOverride) {
                await api.put(`/api/edu-office/schedule/overrides/${state.selectedOverride.id}`, payload);
            } else {
                await api.post('/api/edu-office/schedule/overrides', payload);
            }
        });
    }

    function buildPayload() {
        if (!timeSelect.value) {
            showAlert('schedule-override-alert', 'Выберите время занятия для выбранной даты', 'error');
            return null;
        }
        const dateChanged = state.targetDate !== state.source.lessonDate;
        const timeChanged = String(timeSelect.value) !== String(state.source.timeSlotId);
        const teacherChanged = String(teacherSelect.value) !== String(state.source.teacherId);
        const classroomChanged = String(classroomSelect.value) !== String(state.source.classroomId);
        const formatChanged = formatSelect.value !== state.source.lessonFormat;
        const action = overrideActionForChanges({
            dateChanged,
            timeChanged,
            teacherChanged,
            classroomChanged,
            formatChanged
        });
        if (!action) {
            showAlert(
                'schedule-override-alert',
                state.selectedOverride
                    ? 'Параметры совпадают с правилом. Используйте «Вернуть по правилу»'
                    : 'Измените дату, время, преподавателя, аудиторию или формат',
                'error'
            );
            return null;
        }
        return {
            baseRuleSlotId: state.source.baseRuleSlotId,
            lessonDate: state.source.lessonDate,
            targetLessonDate: dateChanged ? state.targetDate : null,
            action,
            newTimeSlotId: (dateChanged || timeChanged) ? Number(timeSelect.value) : null,
            newTeacherId: teacherChanged ? Number(teacherSelect.value) : null,
            newClassroomId: classroomChanged ? Number(classroomSelect.value) : null,
            newLessonFormat: formatChanged ? formatSelect.value : null,
            comment: commentInput.value.trim() || null
        };
    }

    async function cancelLesson() {
        if (state.busy || !state.source) return;
        const payload = {
            baseRuleSlotId: state.source.baseRuleSlotId,
            lessonDate: state.source.lessonDate,
            targetLessonDate: null,
            action: 'CANCEL',
            newTimeSlotId: null,
            newTeacherId: null,
            newClassroomId: null,
            newLessonFormat: null,
            comment: commentInput.value.trim() || null
        };
        await mutate(async () => {
            if (state.selectedOverride) {
                await api.put(`/api/edu-office/schedule/overrides/${state.selectedOverride.id}`, payload);
            } else {
                await api.post('/api/edu-office/schedule/overrides', payload);
            }
        });
    }

    async function restoreRule() {
        if (state.busy || !state.selectedOverride) return;
        await mutate(() => api.delete(`/api/edu-office/schedule/overrides/${state.selectedOverride.id}`));
    }

    async function mutate(operation) {
        setBusy(true);
        hideAlert('schedule-override-alert');
        try {
            await operation();
            await onChanged?.();
            state.selectedLesson = null;
            state.selectedOverride = null;
            state.source = null;
            form.hidden = true;
            empty.hidden = false;
            empty.textContent = 'Выберите занятие в расписании или откройте его из реестра изменений.';
            openRegistry();
        } catch (error) {
            showAlert('schedule-override-alert', error.message || 'Не удалось сохранить разовое изменение', 'error');
        } finally {
            setBusy(false);
        }
    }

    function setBusy(busy) {
        state.busy = busy;
        saveButton.disabled = busy;
        cancelButton.disabled = busy;
        restoreButton.disabled = busy;
        saveButton.textContent = busy ? 'Сохранение...' : 'Сохранить';
    }

    function renderRegistry() {
        if (!registry) return;
        if (!state.overrides.length) {
            registry.innerHTML = '<div class="schedule-override-empty">За выбранный период разовых изменений нет.</div>';
            return;
        }
        registry.innerHTML = state.overrides.map(override => {
            const target = override.targetLessonDate
                ? ` → ${formatDate(override.targetLessonDate)}`
                : '';
            const result = override.action === 'CANCEL'
                ? 'Занятие отменено'
                : [
                    override.newTimeSlotId ? `слот ${override.newTimeSlotId}` : null,
                    teacherName(override.newTeacherId),
                    classroomName(override.newClassroomId),
                    override.newLessonFormat
                ].filter(Boolean).join(' · ');
            return `
                <article class="schedule-override-registry-item">
                    <div class="schedule-override-registry-topline">
                        <span class="schedule-override-action action-${escapeHtml(override.action.toLowerCase())}">${escapeHtml(ACTION_LABELS[override.action] || override.action)}</span>
                        <time>${escapeHtml(formatDate(override.lessonDate))}${escapeHtml(target)}</time>
                    </div>
                    <h4>${escapeHtml(override.subjectName || 'Занятие')}</h4>
                    <p>${escapeHtml([
                        override.lessonTypeName,
                        (override.groupNames || []).join(', '),
                        result || 'Параметры изменены'
                    ].filter(Boolean).join(' · '))}</p>
                    ${override.comment ? `<blockquote>${escapeHtml(override.comment)}</blockquote>` : ''}
                    <button type="button" class="btn btn-md btn-secondary" data-override-registry-id="${escapeHtml(override.id)}">Открыть</button>
                </article>
            `;
        }).join('');
    }

    function activateTab(tab) {
        drawer.querySelectorAll('[data-override-tab]').forEach(button => {
            button.classList.toggle('active', button.dataset.overrideTab === tab);
        });
        drawer.querySelectorAll('[data-override-section]').forEach(section => {
            section.hidden = section.dataset.overrideSection !== tab;
        });
    }

    function open() {
        drawer.hidden = false;
        drawer.setAttribute('aria-hidden', 'false');
        document.body.classList.add('schedule-override-open');
    }

    function close() {
        drawer.hidden = true;
        drawer.setAttribute('aria-hidden', 'true');
        document.body.classList.remove('schedule-override-open');
    }

    function teacherName(id) {
        if (id == null || id === '') return '';
        const teacher = (dictionaries.teachers || []).find(item => String(item.id) === String(id));
        return teacher?.fullName || teacher?.username || '';
    }

    function classroomName(id) {
        if (id == null || id === '') return '';
        const classroom = (dictionaries.classrooms || []).find(item => String(item.id) === String(id));
        return classroom ? classroomOptionLabel(classroom) : '';
    }
}

function renderSemesterCalendar(startDate, endDate, available, selectedDate) {
    if (!startDate || !endDate) return '';
    const start = parseIsoDate(startDate);
    const end = parseIsoDate(endDate);
    const months = [];
    for (let cursor = new Date(start.getFullYear(), start.getMonth(), 1);
         cursor <= end;
         cursor = new Date(cursor.getFullYear(), cursor.getMonth() + 1, 1)) {
        months.push(renderMonth(cursor, startDate, endDate, available, selectedDate));
    }
    return months.join('');
}

function renderMonth(month, semesterStart, semesterEnd, available, selectedDate) {
    const year = month.getFullYear();
    const monthIndex = month.getMonth();
    const firstDay = new Date(year, monthIndex, 1).getDay() || 7;
    const dayCount = new Date(year, monthIndex + 1, 0).getDate();
    const blanks = Array.from({ length: firstDay - 1 }, () => '<span class="schedule-override-calendar-blank"></span>').join('');
    const days = Array.from({ length: dayCount }, (_, index) => {
        const date = toIsoDate(new Date(year, monthIndex, index + 1));
        const enabled = date >= semesterStart && date <= semesterEnd && available.has(date);
        const active = date === selectedDate ? ' active' : '';
        return `<button type="button" class="schedule-override-calendar-day${active}" data-override-date="${date}"${enabled ? '' : ' disabled'}>${index + 1}</button>`;
    }).join('');
    return `
        <section class="schedule-override-month">
            <h4>${MONTH_LABELS[monthIndex]} ${year}</h4>
            <div class="schedule-override-calendar-grid">
                ${DAY_LABELS.map(label => `<strong>${label}</strong>`).join('')}
                ${blanks}${days}
            </div>
        </section>
    `;
}

function timeSlotLabel(slot) {
    const order = slot.orderNumber ? `${slot.orderNumber} пара · ` : '';
    return `${order}${intervalLabel(slot.startTime, slot.endTime)}`;
}

function intervalLabel(start, end) {
    return `${trimTime(start)}–${trimTime(end)}`;
}

export function classroomOptionLabel(room) {
    return room?.name || '';
}

function ensureSelectOption(select, value, label) {
    if (value == null || value === '') return;
    const exists = Array.from(select.options).some(option => String(option.value) === String(value));
    if (exists) return;
    const option = document.createElement('option');
    option.value = String(value);
    option.textContent = label;
    select.append(option);
}

function formatDate(value) {
    if (!value) return '—';
    const [year, month, day] = String(value).split('-');
    return `${day}.${month}.${year}`;
}

function formatDateShort(value) {
    const [, month, day] = String(value).split('-');
    return `${day}.${month}`;
}

function trimTime(value) {
    return String(value || '').slice(0, 5);
}

function parseIsoDate(value) {
    const [year, month, day] = String(value).split('-').map(Number);
    return new Date(year, month - 1, day);
}

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

function toIsoDate(date) {
    return [
        date.getFullYear(),
        String(date.getMonth() + 1).padStart(2, '0'),
        String(date.getDate()).padStart(2, '0')
    ].join('-');
}
