# library για σύνδεση και κατέβασμα αρχείων από το myschool
# στόχος είναι το κάθε script να μπορεί να κάνει πχ:
# driver = get_driver(headless=True)
# download_report(driver, "Γενική τμημάτων","με μητέρα","target_file.xls")

import pathlib, time, os, re
from contextlib import contextmanager

from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import (
    TimeoutException,
    NoSuchElementException,
    StaleElementReferenceException
)
from selenium.webdriver.firefox.service import Service as FirefoxService
from gr_sch_helper import sch, constants
from gr_sch_helper.sch import get_config

# Τα id του mySchool για την κάθε τάξη
grade_links = {
    "ΑΛ":"ctl00_ContentData_lstLevel_6043_D",
    "ΒΛ":"ctl00_ContentData_lstLevel_6044_D",
    "ΓΛ":"ctl00_ContentData_lstLevel_6045_D",
}

report_shortcodes = constants.report_shortcodes
page_urls = constants.page_urls

report_types = {
    "csv":"ReportToolbar1_Menu_ITCNT13_SaveFormat_DDD_L_LBI7T0",
}

absenseJustificationTypeId = {
    "0":0,            #<empty string> debugger eval code:6:13
    "άρθρο 21":1279,  # Περ. αρθ. 21 της 102791/ΓΔ4 (ΦΕΚ 5130/τ.Β΄/10-09-2024) debugger eval code:6:13
    "άρθρο 23":1328,  # Περ. αρθ. 23 της 102791/ΓΔ4 (ΦΕΚ 5130/τ.Β΄/10-09-2024) debugger eval code:6:13
    "άρθρο 25":1377,  # Περ. αρθ. 25 της 102791/ΓΔ4 (ΦΕΚ 5130/τ.Β΄/10-09-2024) debugger eval code:6:13
    "γιατρός":1426,   # Ιατρική Βεβαίωση {Αρθ. 26 της 102791/ΓΔ4 (ΦΕΚ 5130/τ.Β΄/10-09-2024)} debugger eval code:6:13
    "κηδεμόνας":1475, # Υπεύθυνη Δήλωση Κηδεμόνα {(Αρθ. 26 της 102791/ΓΔ4 (ΦΕΚ 5130/τ.Β΄/10-09-2024} debugger eval code:6:13
    "γρίπη":1531,     # Λόγω εποχικών ιογενών αναπνευστικών λοιμώξεων
}

absenseTypeId = {
     "0":0,         #  <empty string>
     "Ωριαία":630,  # Προφορική παρατήρηση με ωριαία απομάκρυνση
     "1 μέρα":698,  # Αποβολή από τα μαθήματα μιας (01) ημέρας 
     "2 μέρες":747, # Αποβολή από τα μαθήματα δυο (02) ημερών
     "3 μέρες":796, # Αποβολή από τα μαθήματα τριών (03) ημερών
     "4 μέρες":845, # Αποβολή από τα μαθήματα τεσσάρων (04) ημερών
     "5 μέρες":894, # Αποβολή από τα μαθήματα πέντε (05) ημερών
}

# script για γρήγορη καταχώρηση απουσιών
class js_scripts:
    apousies_prep_script = re.sub(r"\s+"," ","""
 window.FIELDS = {
 DateFrom: "DateFrom",
 Qty:                               "Qty",
 JustifiedQty:                      "JustifiedQty",
 StudentAbsenceJustificationTypeId: "StudentAbsenceJustificationTypeId",
 StudentAbsenceTypeId:              "StudentAbsenceTypeId",
};

window.autoEditQueue = {
    _queue: [],
    _busy: false,

    enqueue(rowIndex, values) {
        this._queue.push({ rowIndex, values });
        this._process();
    },

    _process() {
        if (this._busy || this._queue.length === 0) return;
        this._busy = true;
        const { rowIndex, values } = this._queue.shift();
        this._run(rowIndex, values);
    },

    _run(rowIndex, values) {
        function onEditFormReady(s, e) {
            s.EndCallback.RemoveHandler(onEditFormReady);

            Object.values(FIELDS).forEach(field => {
                if (values[field] != null) s.SetEditValue(field, values[field]);
            });

            s.EndCallback.AddHandler(onUpdateFinished);
            s.UpdateEdit();
        }

        function onUpdateFinished(s, e) {
            s.EndCallback.RemoveHandler(onUpdateFinished);
            autoEditQueue._busy = false;
            autoEditQueue._process();
        }

        gridStudentAbsenceSummary.EndCallback.AddHandler(onEditFormReady);
        if (rowIndex === -1) {
            gridStudentAbsenceSummary.AddNewRow();
        }
        else {
            gridStudentAbsenceSummary.StartEditRow(rowIndex); 
        }
    }
};

window.startAutoEdit = function(rowIndex, values) {
    autoEditQueue.enqueue(rowIndex, values);
};
""")

class MySchool:
    def __init__(self, dl_path, headless):
        self.dl_path = dl_path
        self.st_ids = {}
        js = js_scripts()
        username = get_config("Myschool","Username")
        password = get_config("Myschool","Password")
        firefox_options = Options()
        firefox_options.add_argument("--window-size=1440,900")
        firefox_options.add_argument("--disable-gpu")
        firefox_options.add_argument("--private")
        firefox_options.add_argument("-no-remote") 
        firefox_options.add_argument("--disable-extensions")
        firefox_options.set_preference("browser.download.folderList", 2)
        firefox_options.set_preference("browser.download.dir", str(dl_path))
        if headless: firefox_options.add_argument("--headless")

        self.driver = webdriver.Firefox(
            options=firefox_options,
        )
        self.wait =  WebDriverWait(self.driver, timeout=5, poll_frequency=.2)
        self.driver.delete_all_cookies()
        self.driver.get("https://myschool.sch.gr/")
        self.driver.find_element(By.LINK_TEXT, "Σύνδεση").click()
        WebDriverWait(self.driver, 30).until(lambda d: bool(d.find_element(By.ID, "username")))
        self.driver.find_element(By.ID, "username").send_keys(username)
        WebDriverWait(self.driver, 30).until(lambda d: bool(d.find_element(By.ID, "password")))
        self.driver.find_element(By.ID, "password").send_keys(password)
        WebDriverWait(self.driver, 30).until(lambda d: bool(d.find_element(By.ID, "loginButton")))
        self.driver.find_element(By.ID, "loginButton").click()
        self.report_name = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        self.driver.quit()

    def set_st_ids(self, st_ids):
        self.st_ids = st_ids
        
    def print_cookies(self):
        print(self.driver.get_cookies())
        
    def wait_get_one(self, by_type, by_string):
        try:
            WebDriverWait(self.driver, 60).until(EC.visibility_of_element_located((by_type, by_string)))
            return self.driver.find_element(by_type, by_string)
        except (TimeoutException, NoSuchElementException, StaleElementReferenceException):
            return False

    def wait_get_many(self, by_type, by_string):
        self.wait.until(lambda d: bool(d.find_elements(by_type, by_string)))
        return self.driver.find_elements(by_type, by_string)
    
    def goto(self,report_name):
        self.report_name = report_name
        self.driver.get(report_shortcodes[report_name].url)

    def get_report(self,report_name):
        self.report_name = report_name
        self.driver.get(report_shortcodes[report_name].url)

    def get_page(self,page_name):
        self.driver.get(page_urls[page_name])

    def get_student_page(self, page_name, am):
        self.driver.get(f"{page_urls[page_name]}?id={self.st_ids[am]}")
        
    def provoli(self,provoli):
        self.driver.find_element(By.ID,"ctl00_ContentData_cmbViews_I").click()
        elements = self.driver.find_elements(By.CLASS_NAME, "dxeListBoxItem")
        target = next((el for el in elements if el.text.strip() == provoli), None)
        target.click()

    def select_grades(self,grades):
        for grade in grades:
            self.driver.find_element(By.ID,grade_links[grade]).click()
            WebDriverWait(self.driver, 60).until(EC.element_to_be_clickable((By.ID, "ctl00_ContentData_cbpStudents_lstStudents_LBI1C")))

    def open_extra_criteria(self):
        self.driver.find_element(By.ID,"ctl00_ContentData_btnCriteriaPlus").click()
        iframe = WebDriverWait(self.driver, 20).until(EC.presence_of_element_located((By.TAG_NAME, "iframe")))
        self.driver.switch_to.frame(iframe)
        WebDriverWait(self.driver, 20).until(EC.presence_of_element_located((By.XPATH,"//a[normalize-space()='Αποδοχή']")))

    def close_extra_criteria(self):
        self.driver.find_element(By.XPATH,"//a[normalize-space()='Αποδοχή']").click()
        self.driver.switch_to.default_content()

    def set_dates(self,date_from,date_to):
        self.open_extra_criteria()
        from_input = self.wait_get_one(By.ID,"cbpCriteria_dtDateFrom_I")
        from_input.send_keys(date_from)
        to_input = self.wait_get_one(By.ID,"cbpCriteria_dtDateTo_I")
        to_input.send_keys(date_to)
        self.close_extra_criteria()
        
    def click_until(self, elem_id, target_class, max_tries):
        wait = WebDriverWait(self.driver, 30)
        tries = 0
        while tries < max_tries:
            elem = wait.until(EC.element_to_be_clickable((By.ID, elem_id)))
            classes = elem.get_attribute("class").split()
            if target_class in classes:
                break
            elem.click()
            tries += 1
            time.sleep(0.2)
            
    def get_type(self,report_type):
        WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable((By.ID, "ctl00_ContentData_navig_preview")))
        self.driver.find_element(By.ID, "ctl00_ContentData_navig_preview").click()
        filename = report_shortcodes[self.report_name].basename + "." + report_type
        downloadedFile = pathlib.Path(self.dl_path / filename)
        WebDriverWait(self.driver, 60).until(
            EC.visibility_of_element_located((By.ID, "ReportViewer1_ContentFrame"))
        )
        self.driver.execute_script(f"""
var cb = aspxGetControlCollection().Get('ReportToolbar1_Menu_ITCNT13_SaveFormat');
cb.SetValue('{report_type}');
        """)
        self.driver.find_element(By.ID, "ReportToolbar1_Menu_DXI11_").click()
        wait_download_complete(downloadedFile)
        return downloadedFile

    def get_plain(self):
        filename = report_shortcodes[self.report_name].basename + ".xls"
        downloadedFile = pathlib.Path(self.dl_path / filename)
        # if file exists mv to filename.back.n
        self.driver.find_element(By.ID, "ctl00_ContentData_navig_preview").click()
        WebDriverWait(self.driver, 60).until(lambda d: os.path.exists(downloadedFile))
        return downloadedFile

    def get_id_from_am(self,am):
        result = 0
        # wait for it
        inp = self.driver.find_element(By.ID,"ctl00_ContentData_txtRegistrationNo_I")
        inp.send_keys(str(am))
        WebDriverWait(self.driver, 60).until(EC.element_to_be_clickable((By.XPATH,
                                                                         "//a[normalize-space(text())='Αναζήτηση']"
                                        )))
        element = self.driver.find_element(By.XPATH,
                                         "//a[normalize-space(text())='Αναζήτηση']"
                                         )
        element.click()
        wait = WebDriverWait(self.driver, 10)
        try:
            wait.until(
                lambda d: len(self.driver.find_elements(
                    By.XPATH,
                    f"//tr[@id='ctl00_ContentData_gridResults_DXDataRow0']"
                    f"/td[contains(normalize-space(.), '{am}')]"
                )) > 0
            )
            st_row = self.driver.find_element(By.ID,'ctl00_ContentData_gridResults_cell0_0_edit')
            res = st_row.get_property("href").split("=")[-1]
        except TimeoutException:
            res = 0
        element = self.driver.find_element(By.XPATH,
                                         "//a[normalize-space(text())='Καθαρισμός κριτηρίων']")
        element.click()
        return res

    def apousies_prep(self):
        self.driver.execute_script(constants.js_scripts.apousies_prep_script)

    def apousies_get_date(self, dateof):
        xpath = (
            "//tr[contains(@id,'gridStudentAbsenceSummary_DXDataRow')]"
            f"[.//td[contains(normalize-space(.), \"{dateof}\")]]"
        )
        row = WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.XPATH, xpath))
        )

        row_id = row.get_attribute("id")
        return row_id.split("DXDataRow")[-1]

    def apousies_set(self, rowIndex, params):
        apousies_set_script = f'window.autoEditDone = false; (async () => {{await startAutoEdit({rowIndex}, {{ {params} }}); window.autoEditDone = true;}})();'
        print(apousies_set_script)
        self.driver.execute_script(apousies_set_script)
        WebDriverWait(self.driver, 30).until(
            lambda d: d.execute_script("return window.autoEditDone === true")
        )
        
def get_webdriver(dl_path, headless):
    return MySchool(dl_path, headless)

def get_test_webdriver(dl_path):
    return MySchool(dl_path, headless=False)

def wait_download_complete(path, timeout=120):
    start = time.time()
    last_size = -1
    
    while time.time() - start < timeout:
        if path.exists():
            size = path.stat().st_size
            if size > 0 and size == last_size:
                return
            last_size = size
        time.sleep(2)
    raise TimeoutError(f"Download not completed: {path}")
