import axios from "axios";
import { load } from 'cheerio';
import { Food } from "../../types";
import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock } from "./mock";

// 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';

/**
 * 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
 */
const isTextSoupName = (text: string): boolean => {
    for (const name of SOUP_NAMES) {
        if (text.toLowerCase().includes(name)) {
            return true;
        }
    }
    return false;
}

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

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

/**
 * 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);

    const list = $('ul.tab-links').children();
    const result: Food[][] = [];
    for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
        const currentDate = new Date(firstDayOfWeek);
        currentDate.setDate(firstDayOfWeek.getDate() + dayIndex);
        const searchedDayText = `${currentDate.getDate()}.${currentDate.getMonth() + 1}.${capitalize(DAYS_IN_WEEK[dayIndex])}`;
        // Najdeme index pro vstupní datum (např. při svátcích bude posunutý)
        // TODO validovat, že vstupní datum je v aktuálním týdnu
        // TODO tenhle způsob je zbytečně komplikovaný - stačilo by hledat rovnou v div.tab-content, protože každý den tam má datum taky (akorát je print-only)
        let index = undefined;
        list.each((i, dayRow) => {
            const rowText = $(dayRow).first().text().trim();
            if (rowText === searchedDayText) {
                index = i;
                return;
            }
        })
        if (index === undefined) {
            // Pravděpodobně svátek, nebo je zavřeno
            result[dayIndex] = [{
                amount: undefined,
                name: "Pro daný den nebyla nalezena denní nabídka",
                price: "",
                isSoup: false,
            }];
            continue;
        }

        // Dle dohledaného indexu najdeme správný tabpanel
        const rows = $('div.tab-content').children();
        if (index >= rows.length) {
            throw Error("V HTML nebyl nalezen řádek menu pro index " + index);
        }
        const tabPanel = $(rows.get(index));

        // Opětovná validace, že daný tabpanel je pro vstupní datum
        const headers = tabPanel.find('h2');
        if (headers.length !== 3) {
            throw Error("Neočekávaný počet elementů h2 v menu pro datum " + searchedDayText + ", očekávány 3, ale nalezeno bylo " + headers.length);
        }
        const dayText = $(headers.get(0)).text().trim();
        if (dayText !== searchedDayText) {
            throw Error("Neočekávaný datum na řádce nalezeného dne: '" + dayText + "', ale očekáváno bylo '" + searchedDayText + "'");
        }

        // V tabpanelu očekáváme dvě tabulky - pro polévku a pro hlavní jídlo 
        const tables = tabPanel.find('table');
        if (tables.length !== 2) {
            throw Error("Neočekávaný počet tabulek na řádce nalezeného dne: " + tables.length + ", ale očekávány byly 2");
        }
        const currentDayFood: Food[] = [];
        // Polévka - div -> table -> tbody -> tr -> 3x td
        const soupCells = $(tables.get(0)).children().first().children().first().children();
        if (soupCells.length !== 3) {
            throw Error("Neočekávaný počet buněk v tabulce polévky: " + soupCells.length + ", ale očekávány byly 3");
        }
        currentDayFood.push({
            amount: sanitizeText($(soupCells.get(0)).text()),
            name: sanitizeText($(soupCells.get(1)).text()),
            price: sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')),
            isSoup: true,
        });
        // Hlavní jídla - div -> table -> tbody -> 3x tr
        const mainCourseRows = $(tables.get(1)).children().first().children();
        mainCourseRows.each((i, foodRow) => {
            const foodCells = $(foodRow).children();
            if (foodCells.length !== 3) {
                throw Error("Neočekávaný počet buněk v řádku jídla: " + foodCells.length + ", ale očekávány byly 3");
            }
            currentDayFood.push({
                amount: sanitizeText($(foodCells.get(0)).text()),
                name: sanitizeText($(foodCells.get(1)).text()),
                price: sanitizeText($(foodCells.get(2)).text().replace(' ', '\xA0')),
                isSoup: false,
            });
        })
        result[dayIndex] = 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.getTime());
    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 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 Error("Neočekáváný typ jídla: " + foodType);
                    }
                } else {
                    if (children.length !== 3) {
                        throw 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;
    // 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"
    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;
            }
        })
    }
    if (!font) {
        throw Error('Chyba: nenalezen <font> pro obědy v HTML Techtower.');
    }

    const result: Food[][] = [];
    // TODO validovat, že v textu nalezeného <font> je rozsah, do kterého spadá vstupní datum
    const siblings = secondTry ? $(font).parent().siblings() : $(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 name = 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č`
                name = split[0].replace('•', '');
            }
            if (result[currentDayIndex] == null) {
                result[currentDayIndex] = [];
            }
            result[currentDayIndex].push({
                amount: '-',
                name,
                price,
                isSoup: isTextSoupName(name),
            })
        }
    }
    return result;
}