import axios from "axios";
import { load } from 'cheerio';
import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock, getMenuZastavkaUmichalaMock, getMenuSenkSerikovaMock } from "./mock";
import { formatDate } from "./utils";
import { Food } from "../../types/gen/types.gen";

export class StaleWeekError extends Error {
    constructor(public food: Food[][]) { super('Data jsou z jiného týdne'); }
}

// Fráze v názvech jídel, které naznačují že se jedná o polévku
const SOUP_NAMES = [
    'polévka',
    'česnečka',
    'česnekový krém',
    'cibulačka',
    'vývar',
    'fazolová',
    'cuketový krém',
    'boršč',
    'slepičí s ',
    'zeleninová s ',
    'hovězí s ',
    'kachní kaldoun',
    'dršťková'
];
const DAYS_IN_WEEK = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota', 'neděle'];

// URL na týdenní menu jednotlivých restaurací
const SLADOVNICKA_URL = 'https://sladovnicka.unasplzenchutna.cz/cz/#denni-nabidka';
const U_MOTLIKU_URL = 'https://www.umotliku.cz/menu';
const TECHTOWER_URL = 'https://www.equifarm.cz/restaurace-techtower';
const ZASTAVKAUMICHALA_URL = 'https://www.zastavkaumichala.cz';
const SENKSERIKOVA_URL = 'https://www.menicka.cz/6561-pivovarsky-senk-serikova.html';

/**
 * Vrátí true, pokud předaný text obsahuje některé ze slov, které naznačuje, že se jedná o polévku.
 * Využito tam, kde nelze polévku identifikovat lepším způsobem (TechTower).
 * 
 * @param text vstupní text
 * @returns true, pokud text představuje polévku
 */
export const isTextSoupName = (text: string): boolean => {
    for (const name of SOUP_NAMES) {
        if (text.toLowerCase().includes(name)) {
            return true;
        }
    }
    return false;
}

export const capitalize = (word: string): string => {
    return word.charAt(0).toUpperCase() + word.slice(1);
}

export const sanitizeText = (text: string): string => {
    return text.replace('\t', '').replace(' , ', ', ').trim();
}

/**
 * Parsuje čísla alergenů z názvu jídla a vrací vyčištěný název spolu s polem alergenů.
 * Alergeny jsou očekávány na konci názvu ve formátu číslic oddělených čárkami.
 * 
 * @param name původní název jídla
 * @returns objekt obsahující vyčištěný název a pole alergenů
 */
export const parseAllergens = (name: string): { cleanName: string, allergens: number[] } => {
    // Regex pro nalezení čísel na konci řetězce oddělených čárkami a případnými mezerami
    const regex = /\s+(\d+(?:\s*,\s*\d+)*)\s*$/;
    const match = regex.exec(name);
    
    if (match) {
        const allergenString = match[1];
        const allergens = allergenString.split(',').map(num => Number.parseInt(num.trim(), 10)).filter(num => !Number.isNaN(num));
        const cleanName = name.replace(regex, '').trim();
        return { cleanName, allergens };
    }
    
    return { cleanName: name, allergens: [] };
}

/**
 * Stáhne a vrátí aktuální HTML z dané URL.
 * 
 * @param url URL pro stažení
 * @returns stažené HTML
 */
const getHtml = async (url: string): Promise<any> => {
    return await axios.get(url).then(res => res.data).then(content => content);
}

/**
 * Získá obědovou nabídku Sladovnické pro jeden týden.
 * 
 * @param firstDayOfWeek první den v týdnu, pro který získat menu
 * @param mock zda vrátit mock data
 * @returns seznam jídel pro daný týden
 */
export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
    if (mock) {
        return getMenuSladovnickaMock();
    }

    const html = await getHtml(SLADOVNICKA_URL);
    const $ = load(html);

    // Zjistíme, které dny jsou k dispozici z tab elementů
    const tabElements = $('#daily-menu-tab-list').children('button[id^="daily-menu-tab-"]');
    const availableDays: { [dayIndex: number]: number } = {}; // mapování dayIndex -> contentIndex
    
    tabElements.each((contentIndex, tabElement) => {
        const dayText = $(tabElement).find('.daily-menu-tab__day').text().toLowerCase();
        const dayIndex = DAYS_IN_WEEK.indexOf(dayText);
        if (dayIndex !== -1 && dayIndex < 5) { // pouze pracovní dny (0-4)
            availableDays[dayIndex] = contentIndex;
        }
    });

    const menuContentElements = $('#daily-menu-content-list').children('.daily-menu-content__content').not('.daily-menu-content__content--static');
    
    const result: Food[][] = [];
    
    // Inicializujeme všechny pracovní dny (0-4) prázdnými poli
    for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
        result[dayIndex] = [];
    }
    
    // Projdeme pouze dostupné dny
    for (const [dayIndex, contentIndex] of Object.entries(availableDays)) {
        const dayIndexNum = Number.parseInt(dayIndex);
        const contentIndexNum = contentIndex;
        
        if (contentIndexNum >= menuContentElements.length) {
            continue; // Přeskočíme, pokud content element neexistuje
        }
        
        const contentElement = $(menuContentElements[contentIndexNum]);
        const itemElement = contentElement.find('.daily-menu-content__item');
        const table = itemElement.find('table.daily-menu-content__table tbody');
        const rows = table.children('tr');
        
        const currentDayFood: Food[] = [];
        
        // Projdeme všechny řádky - první je polévka, zbytek jsou hlavní jídla
        rows.each((i, row) => {
            const cells = $(row).children('td');
            if (cells.length !== 3) {
                return; // Přeskočíme řádky s nesprávnou strukturou
            }
            
            const amount = sanitizeText($(cells.get(0)).text());
            const nameRaw = sanitizeText($(cells.get(1)).text());
            const price = sanitizeText($(cells.get(2)).text().replace(' ', '\xA0'));
            const parsed = parseAllergens(nameRaw);
            
            // Přeskočíme prázdné řádky
            if (parsed.cleanName.trim().length > 0) {
                currentDayFood.push({
                    amount,
                    name: parsed.cleanName,
                    price,
                    isSoup: i === 0, // První řádek je polévka
                    allergens: parsed.allergens.length > 0 ? parsed.allergens : undefined,
                });
            }
        });

        result[dayIndexNum] = currentDayFood;
    }
    
    return result;
}

/**
 * Získá obědovou nabídku restaurace U Motlíků pro jeden týden.
 * 
 * @param firstDayOfWeek první den v týdnu, pro který získat menu
 * @param mock zda vrátit mock data
 * @returns seznam jídel pro dané datum
 */
export const getMenuUMotliku = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
    if (mock) {
        return getMenuUMotlikuMock();
    }

    const html = await getHtml(U_MOTLIKU_URL);
    const $ = load(html);

    // Najdeme první tabulku, nad kterou je v H3 datum začínající co nejdřívějším dnem v aktuálním týdnu
    const tables = $('table.table.table-hover.Xtable-striped');
    let usedTable;
    let usedDate = new Date(firstDayOfWeek);
    for (let i = 0; i < 4; i++) {
        const dayOfWeekString = `${usedDate.getDate()}.${usedDate.getMonth() + 1}.`;
        for (const tableNode of tables) {
            const table = $(tableNode);
            const h3 = table.parent().prev();
            const s1 = h3.text().split("-")[0].split(".");
            const foundFirstDayString = `${s1[0]}.${s1[1]}.`;
            if (foundFirstDayString === dayOfWeekString) {
                usedTable = table;
            }
        }
        if (usedTable != null) {
            break;
        }
        usedDate.setDate(usedDate.getDate() + 1);
    }

    if (usedTable == null) {
        const firstDayOfWeekString = `${firstDayOfWeek.getDate()}.${firstDayOfWeek.getMonth() + 1}.`;
        throw new Error(`Nepodařilo se najít tabulku pro týden začínající ${firstDayOfWeekString}`);
    }

    const body = usedTable.children().first();
    const rows = body.children();

    const result: Food[][] = [];
    for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
        if (!(dayIndex in result)) {
            result[dayIndex] = [];
        }
        let parsing = false;
        let isSoup = false;
        rows.each((i, row) => {
            const firstChild = $(row).children().get(0);
            if (firstChild?.name == 'th') {
                const childText = $(firstChild).text();
                if (capitalize(DAYS_IN_WEEK[dayIndex]) === childText) {
                    parsing = true;
                } else if (parsing) {
                    // Narazili jsme na další den - konec parsování
                    parsing = false;
                    return;
                }
            } else if (parsing) {
                const children = $(row).children();
                if (children.length === 1) { // Nadpis "Polévka" nebo "Hlavní jídlo"
                    const foodType = children.first().text();
                    if (foodType === 'Polévka') {
                        isSoup = true;
                    } else if (foodType === 'Hlavní jídlo') {
                        isSoup = false;
                    } else {
                        throw new Error("Neočekáváný typ jídla: " + foodType);
                    }
                } else {
                    if (children.length !== 3) {
                        throw new Error("Neočekávaný počet child elementů pro jídlo: " + children.length + ", očekávány 3");
                    }
                    const amount = sanitizeText($(children.get(0)).text());
                    const name = sanitizeText($(children.get(1)).text());
                    const price = sanitizeText($(children.get(2)).text()).replace(',-', '').replace(' ', '\xA0');
                    result[dayIndex].push({
                        amount,
                        name,
                        price,
                        isSoup,
                    })
                }
            }
        })
    }
    return result;
}

/**
 * Získá obědovou nabídku TechTower pro jeden týden.
 * 
 * @param firstDayOfWeek první den v týdnu, pro který získat menu
 * @param mock zda vrátit mock data
 * @returns seznam jídel pro dané datum
 */
export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
    if (mock) {
        return getMenuTechTowerMock();
    }

    const html = await getHtml(TECHTOWER_URL);
    const $ = load(html);

    let secondTry = false;
    let thirdTry = false;
    // První pokus - varianta "Obědy"
    let fonts = $('font.wsw-41');
    let font = undefined;
    fonts.each((i, f) => {
        if ($(f).text().trim().startsWith('Obědy')) {
            font = f;
        }
    })
    // Druhý pokus - varianta "Jídelní lístek" (starší formát)
    if (!font) {
        fonts = $('font.wnd-font-size-90');
        fonts.each((i, f) => {
            if ($(f).text().trim().startsWith('Jídelní lístek')) {
                font = f;
                secondTry = true;
            }
        })
    }
    // Třetí pokus - nový formát: font.wsw-41 s textem "Jídelní lístek" (vše v jednom bloku)
    if (!font) {
        fonts = $('font.wsw-41');
        fonts.each((i, f) => {
            if ($(f).text().trim().startsWith('Jídelní lístek')) {
                font = f;
                thirdTry = true;
            }
        })
    }
    if (!font) {
        throw new Error('Chyba: nenalezen <font> pro obědy v HTML Techtower.');
    }

    const result: Food[][] = [];
    const siblings = thirdTry
        ? $(font).parent().siblings('p')
        : secondTry
            ? $(font).parent().parent().parent().siblings('p')
            : $(font).parent().parent().siblings();
    let parsing = false;
    let currentDayIndex = 0;
    for (let i = 0; i < siblings.length; i++) {
        const text = $(siblings.get(i)).text().trim().replace('\t', '').replace('\n', ' ');
        if (DAYS_IN_WEEK.includes(text.toLocaleLowerCase())) {
            // Zjistíme aktuální index
            currentDayIndex = DAYS_IN_WEEK.indexOf(text.toLocaleLowerCase());
            if (!parsing) {
                // Našli jsme libovolný den v týdnu a ještě neparsujeme, tak začneme
                parsing = true;
            }
        } else if (parsing) {
            if (text.length == 0) {
                // Prázdná řádka - bývá na zcela náhodných místech ¯\_(ツ)_/¯
                continue;
            }
            let price = 'na\xA0váhu';
            let nameRaw = text.replace('•', '');
            if (text.toLowerCase().endsWith('kč')) {
                const tmp = text.replace('\xA0', ' ').split(' ');
                const split = [tmp.slice(0, -2).join(' ')].concat(tmp.slice(-2));
                price = `${split.slice(1)[0]}\xA0Kč`
                nameRaw = split[0].replace('•', '');
            } else if (text.toLowerCase().endsWith(',-')) {
                const tmp = text.replace('\xA0', ' ').split(' ');
                const split = [tmp.slice(0, -1).join(' ')].concat(tmp.slice(-1));
                price = `${split.slice(1)[0].replace(',-', '')}\xA0Kč`
                nameRaw = split[0].replace('•', '');
            }
            if (nameRaw.endsWith('–')|| nameRaw.endsWith('—')) {
                nameRaw = nameRaw.slice(0, -1).trim();
            }

            const parsed = parseAllergens(nameRaw);
            result[currentDayIndex] ??= [];
            result[currentDayIndex].push({
                amount: '-',
                name: parsed.cleanName,
                price,
                isSoup: isTextSoupName(parsed.cleanName),
                allergens: parsed.allergens.length > 0 ? parsed.allergens : undefined,
            })
        }
    }

    // Validace, zda text hlavičky obsahuje datum odpovídající požadovanému týdnu
    const headerText = $(font).text().trim();
    const dateMatch = headerText.match(/(\d{1,2})\.(\d{1,2})\./);
    if (dateMatch) {
        const foundDay = parseInt(dateMatch[1]);
        const foundMonth = parseInt(dateMatch[2]) - 1; // JS months are 0-based
        if (foundDay !== firstDayOfWeek.getDate() || foundMonth !== firstDayOfWeek.getMonth()) {
            throw new StaleWeekError(result);
        }
    }

    return result;
}

/**
 * Získá obědovou nabídku ZastavkaUmichala pro jeden týden.
 *
 * @param firstDayOfWeek první den v týdnu, pro který získat menu
 * @param mock zda vrátit mock data
 * @returns seznam jídel pro dané datum
 */
export const getMenuZastavkaUmichala = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
    if (mock) {
        return getMenuZastavkaUmichalaMock();
    }

    const today = new Date();
    today.setHours(0,0,0,0);
    const headers = {
        "Cookie": "_nss=1; PHPSESSID=9e37de17e0326b0942613d6e67a30e69",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
    };
    const result: Food[][] = [];
    for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
        const currentDate = new Date(firstDayOfWeek);
        currentDate.setDate(firstDayOfWeek.getDate() + dayIndex);
        currentDate.setHours(0,0,0,0);
        if (currentDate < today || (currentDate.getTime() === today.getTime() && new Date().getHours() >= 14)) {
            result[dayIndex] = [{
                amount: undefined,
                name: "Pro tento den není uveřejněna nabídka jídel",
                price: "",
                isSoup: false,
            }];
        } else {
            const url = (currentDate.getTime() === today.getTime())
                ? ZASTAVKAUMICHALA_URL
                : ZASTAVKAUMICHALA_URL + '/?do=dailyMenu-changeDate&dailyMenu-dateString=' + formatDate(currentDate, 'DD.MM.YYYY');
            const html = await axios.get(url, {
                headers,
            }).then(res => res.data).then(content => content);
            const $ = load(html);

            const currentDayFood: Food[] = [];
            $('.foodsList li').each((index, element) => {
                currentDayFood.push({
                    amount: '-',
                    name: sanitizeText($(element).contents().not('span').text()),
                    price: sanitizeText($(element).find('span').text()),
                    isSoup: (index === 0),
                });
            });
            result[dayIndex] = currentDayFood;
        }
    }
    return result;
}

/**
 * Získá obědovou nabídku SenkSerikova pro jeden týden.
 *
 * @param firstDayOfWeek první den v týdnu, pro který získat menu
 * @param mock zda vrátit mock data
 * @returns seznam jídel pro dané datum
 */
export const getMenuSenkSerikova = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
    if (mock) {
        return getMenuSenkSerikovaMock();
    }

    const decoder = new TextDecoder('windows-1250');
    const html = await axios.get(SENKSERIKOVA_URL, {
        responseType: 'arraybuffer',
        responseEncoding: 'binary'
    }).then(res => decoder.decode(new Uint8Array(res.data))).then(content => content);
    const $ = load(html);

    const today = new Date();
    today.setHours(0,0,0,0);
    const currentDate = new Date(firstDayOfWeek);
    const result: Food[][] = [];
    let dayIndex = 0;
    currentDate.setHours(0,0,0,0);
    while (currentDate < today) {
        result[dayIndex] = [{
            amount: undefined,
            name: "Pro tento den není uveřejněna nabídka jídel",
            price: "",
            isSoup: false,
        }];
        dayIndex = dayIndex + 1;
        currentDate.setDate(firstDayOfWeek.getDate() + dayIndex);
    }

    $('.menicka').each((i, element) => {
        const currentDayFood: Food[] = [];
        $(element).find('.popup-gallery li').each((j, element) => {
            const rawName = $(element).children('div.polozka').text();
            const nameWithoutNumber = rawName.replace(/^\d+\.\s*/, '');
            currentDayFood.push({
                amount: '-',
                name: nameWithoutNumber,
                price: $(element).children('div.cena').text().replaceAll(' ', '\xA0'),
                isSoup: $(element).hasClass('polevka'),
            });
        });
        result[dayIndex++] = currentDayFood;
    });
    return result;
}
