from datetime import datetime
from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.shared import Pt, RGBColor, Cm
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
import os
from os.path import basename
from loguru import logger as log
import sys
from src import settings


logger = log
logger.remove()
logger.add("logs/application.log", rotation="1 week", retention="1 month", enqueue=True)
log.add(
    f"logs/{datetime.now().strftime('%Y-%m-%d')}.log",
    rotation="1 day",
    compression="zip",
)

# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
logger.add(sys.stdout)

font = "Cascadia Mono"


def print_document(file: str):
    # send document to printer as attachment of email
    import smtplib
    from email.mime.multipart import MIMEMultipart
    from email.mime.application import MIMEApplication
    from email.mime.text import MIMEText

    smtp = settings.mail.smtp_server
    port = settings.mail.port
    sender_email = settings.mail.sender
    password = settings.mail.password
    receiver = settings.mail.printer_mail
    message = MIMEMultipart()
    message["From"] = sender_email
    message["To"] = receiver
    message["cc"] = settings.mail.sender
    message["Subject"] = "."
    mail_body = "."
    message.attach(MIMEText(mail_body, "html"))
    with open(file, "rb") as fil:
        part = MIMEApplication(fil.read(), Name=basename(file))
    # After the file is closed
    part["Content-Disposition"] = 'attachment; filename="%s"' % basename(file)
    message.attach(part)
    mail = message.as_string()
    with smtplib.SMTP_SSL(smtp, port) as server:
        server.connect(smtp, port)
        server.login(settings.mail.user_name, password)
        server.sendmail(sender_email, receiver, mail)
        server.quit()
        log.success("Mail sent")


class SemesterError(Exception):
    """Custom exception for semester-related errors."""

    def __init__(self, message: str):
        super().__init__(message)
        log.error(message)

    def __str__(self):
        return f"SemesterError: {self.args[0]}"


class SemesterDocument:
    def __init__(
        self,
        apparats: list[tuple[int, str]],
        semester: str,
        filename: str,
        full: bool = False,
    ):
        assert isinstance(apparats, list), SemesterError(
            "Apparats must be a list of tuples"
        )
        assert all(isinstance(apparat, tuple) for apparat in apparats), SemesterError(
            "Apparats must be a list of tuples"
        )
        assert all(isinstance(apparat[0], int) for apparat in apparats), SemesterError(
            "Apparat numbers must be integers"
        )
        assert all(isinstance(apparat[1], str) for apparat in apparats), SemesterError(
            "Apparat names must be strings"
        )
        assert isinstance(semester, str), SemesterError("Semester must be a string")
        assert "." not in filename and isinstance(filename, str), SemesterError(
            "Filename must be a string and not contain an extension"
        )
        self.doc = Document()
        self.apparats = apparats
        self.semester = semester
        self.table_font_normal = font
        self.table_font_bold = font
        self.header_font = font
        self.header_font_size = Pt(26)
        self.sub_header_font_size = Pt(18)
        self.table_font_size = Pt(10)
        self.color_red = RGBColor(255, 0, 0)
        self.color_blue = RGBColor(0, 0, 255)
        self.filename = filename
        if full:
            log.info("Full document generation")
            self.cleanup
            log.info("Cleanup done")
            self.make_document()
            log.info("Document created")
            self.create_pdf()
            log.info("PDF created")
            print_document(self.filename + ".pdf")
            log.info("Document printed")

    def set_table_border(self, table):
        """
        Adds a full border to the table.

        :param table: Table object to which the border will be applied.
        """
        tbl = table._element
        tbl_pr = tbl.xpath("w:tblPr")[0]
        tbl_borders = OxmlElement("w:tblBorders")

        # Define border styles
        for border_name in ["top", "left", "bottom", "right", "insideH", "insideV"]:
            border = OxmlElement(f"w:{border_name}")
            border.set(qn("w:val"), "single")
            border.set(qn("w:sz"), "4")  # Thickness of the border
            border.set(qn("w:space"), "0")
            border.set(qn("w:color"), "000000")  # Black color
            tbl_borders.append(border)

        tbl_pr.append(tbl_borders)

    def create_sorted_table(self) -> None:
        # Sort the apparats list by the string in the tuple (index 1)
        self.apparats.sort(key=lambda x: x[1])
        # Create a table with rows equal to the length of the apparats list
        table = self.doc.add_table(rows=len(self.apparats), cols=2)
        table.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

        # Set column widths by directly modifying the cell properties
        widths = [Cm(1.19), Cm(10)]
        for col_idx, width in enumerate(widths):
            for cell in table.columns[col_idx].cells:
                cell_width_element = cell._element.xpath(".//w:tcPr")[0]
                tcW = OxmlElement("w:tcW")
                tcW.set(qn("w:w"), str(int(width.cm * 567)))  # Convert cm to twips
                tcW.set(qn("w:type"), "dxa")
                cell_width_element.append(tcW)

        # Adjust row heights
        for row in table.rows:
            trPr = row._tr.get_or_add_trPr()  # Get or add the <w:trPr> element
            trHeight = OxmlElement("w:trHeight")
            trHeight.set(
                qn("w:val"), str(int(Pt(15).pt * 20))
            )  # Convert points to twips
            trHeight.set(qn("w:hRule"), "exact")  # Use "exact" for fixed height
            trPr.append(trHeight)

        # Fill the table with sorted data
        for row_idx, (number, name) in enumerate(self.apparats):
            row = table.rows[row_idx]

            # Set font for the first column (number)
            cell_number_paragraph = row.cells[0].paragraphs[0]
            cell_number_run = cell_number_paragraph.add_run(str(number))
            cell_number_run.font.name = self.table_font_bold
            cell_number_run.font.size = self.table_font_size
            cell_number_run.font.bold = True
            cell_number_run.font.color.rgb = self.color_red
            cell_number_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

            # Set font for the second column (name)
            cell_name_paragraph = row.cells[1].paragraphs[0]
            words = name.split()
            if words:
                # Add the first word in bold
                bold_run = cell_name_paragraph.add_run(words[0])
                bold_run.font.bold = True
                bold_run.font.name = self.table_font_bold
                bold_run.font.size = self.table_font_size

                # Add the rest of the words normally
                if len(words) > 1:
                    normal_run = cell_name_paragraph.add_run(" " + " ".join(words[1:]))
                    normal_run.font.name = self.table_font_normal
                    normal_run.font.size = self.table_font_size
            cell_name_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT

        self.set_table_border(table)

    def make_document(self):
        # Create a new Document
        section = self.doc.sections[0]
        section.top_margin = Cm(2.54)  # Default 1 inch (can adjust as needed)
        section.bottom_margin = Cm(1.5)  # Set bottom margin to 1.5 cm
        section.left_margin = Cm(2.54)  # Default 1 inch
        section.right_margin = Cm(2.54)  # Default 1 inch

        # Add the current date
        current_date = datetime.now().strftime("%Y-%m-%d")
        date_paragraph = self.doc.add_paragraph(current_date)
        date_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT

        # Add a header
        semester = f"Semesterapparate {self.semester}"
        header = self.doc.add_paragraph(semester)
        header.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
        header_run = header.runs[0]
        header_run.font.name = self.header_font
        header_run.font.size = self.header_font_size
        header_run.font.bold = True
        header_run.font.color.rgb = self.color_blue

        sub_header = self.doc.add_paragraph("(Alphabetisch)")
        sub_header.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
        sub_header_run = sub_header.runs[0]
        sub_header_run.font.name = self.header_font
        sub_header_run.font.size = self.sub_header_font_size
        sub_header_run.font.color.rgb = self.color_red

        self.doc.add_paragraph("")

        self.create_sorted_table()

    def save_document(self, name):
        # Save the document
        self.doc.save(name)

    def create_pdf(self):
        # Save the document
        import comtypes.client

        word = comtypes.client.CreateObject("Word.Application")
        self.save_document(self.filename + ".docx")
        docpath = os.path.abspath(self.filename + ".docx")
        doc = word.Documents.Open(docpath)
        curdir = os.getcwd()
        doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17)
        doc.Close()
        word.Quit()
        log.debug("PDF saved")

    @property
    def cleanup(self):
        if os.path.exists(f"{self.filename}.docx"):
            os.remove(f"{self.filename}.docx")
            os.remove(f"{self.filename}.pdf")

    @property
    def send(self):
        print_document(self.filename + ".pdf")
        log.debug("Document sent to printer")


class SemapSchilder:
    def __init__(self, entries: list[str]):
        self.entries = entries
        self.filename = "Schilder"
        self.font_size = Pt(23)
        self.font_name = font
        self.doc = Document()
        self.define_doc_properties()
        self.add_entries()
        self.cleanup()
        self.create_pdf()

    def define_doc_properties(self):
        # set the doc to have a top margin of 1cm, left and right are 0.5cm, bottom is 0cm
        section = self.doc.sections[0]
        section.top_margin = Cm(1)
        section.bottom_margin = Cm(0)
        section.left_margin = Cm(0.5)
        section.right_margin = Cm(0.5)

        # set the font to Times New Roman, size 23 bold, color black
        for paragraph in self.doc.paragraphs:
            for run in paragraph.runs:
                run.font.name = self.font_name
                run.font.size = self.font_size
                run.font.bold = True
                run.font.color.rgb = RGBColor(0, 0, 0)
                paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

        # if the length of the text is

    def add_entries(self):
        for entry in self.entries:
            paragraph = self.doc.add_paragraph(entry)
            paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
            paragraph.paragraph_format.line_spacing = Pt(23)  # Set fixed line spacing
            paragraph.paragraph_format.space_before = Pt(2)  # Remove spacing before
            paragraph.paragraph_format.space_after = Pt(2)  # Remove spacing after

            run = paragraph.runs[0]
            run.font.name = self.font_name
            run.font.size = self.font_size
            run.font.bold = True
            run.font.color.rgb = RGBColor(0, 0, 0)

            # Add a line to be used as a guideline for cutting
            line = self.doc.add_paragraph()
            line.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
            line.paragraph_format.line_spacing = Pt(23)  # Match line spacing
            line.paragraph_format.space_before = Pt(2)  # Remove spacing before
            line.paragraph_format.space_after = Pt(2)  # Remove spacing after
            line.add_run("--------------------------")

    def save_document(self):
        # Save the document
        self.doc.save(f"{self.filename}.docx")
        log.debug(f"Document saved as {self.filename}.docx")

    def create_pdf(self):
        # Save the document
        import comtypes.client

        word = comtypes.client.CreateObject("Word.Application")
        self.save_document()
        docpath = os.path.abspath(f"{self.filename}.docx")
        doc = word.Documents.Open(docpath)
        curdir = os.getcwd()
        doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17)
        doc.Close()
        word.Quit()
        log.debug("PDF saved")

    def cleanup(self):
        if os.path.exists(f"{self.filename}.docx"):
            os.remove(f"{self.filename}.docx")
        if os.path.exists(f"{self.filename}.pdf"):
            os.remove(f"{self.filename}.pdf")

    @property
    def send(self):
        print_document(self.filename + ".pdf")
        log.debug("Document sent to printer")


if __name__ == "__main__":
    entries = [
        "Lüsebrink (Theorie und Praxis der Leichtathletik)",
        "Kulovics (ISP-Betreuung)",
        "Köhler (Ausgewählte Aspekte der materiellen Kultur Textil)",
        "Grau (Young Adult Literature)",
        "Schiebel (Bewegung II:Ausgewählte Problemfelder)",
        "Schiebel (Ernährungswiss. Perspektive)",
        "Park (Kommunikation und Kooperation)",
        "Schiebel (Schwimmen)",
        "Huppertz (Philosophieren mit Kindern)",
        "Heyl (Heyl)",
        "Reuter (Verschiedene Veranstaltungen)",
        "Reinhold (Arithmetik und mathematisches Denken)",
        "Wirtz (Forschungsmethoden)",
        "Schleider (Essstörungen)",
        "Schleider (Klinische Psychologie)",
        "Schleider (Doktorandenkolloquium)",
        "Schleider (Störungen Sozialverhaltens/Delinquenz)",
        "Burth (EU Forschung im Int. Vergleich/EU Gegenstand biling. Didaktik)",
        "Reinhardt (Einführung Politikdidaktik)",
        "Schleider (Psychologische Interventionsmethoden)",
        "Schleider (ADHS)",
        "Schleider (Beratung und Teamarbeit)",
        "Schleider (LRS)",
        "Schleider (Gesundheitspsychologie)",
        "Schleider (Elterntraining)",
        "Wulff (Hochschulzertifikat DaZ)",
        "Dinkelaker ( )",
        "Droll (Einführung in die Sprachwissenschaft)",
        "Karoß (Gymnastik - Sich Bewegen mit und ohne Handgeräte)",
        "Sahrai (Kindheit und Gesellschaft)",
    ]
    doc = SemapSchilder(entries)