import datetime
import os
import re
from pathlib import Path

import pypandoc


def create_markdown_report(log_filename: Path, output_name: Path, output_dir: Path):
    """
    Generates a markdown report from a specified log file.

    This function reads a log file, processes the content by adding markdown image syntax for any detected plot paths,
    and then writes the processed content into a markdown file named after the `output_name` parameter in the specified
    `output_dir`. The report includes a header with the current date and the processed log content.

    Parameters
    ----------
    log_filename : Path
        The path to the log file to be processed.
    output_name : Path
        The base name for the output markdown file (without extension).
    output_dir : Path
        The directory where the markdown file will be saved.
    """
    with open(log_filename, "r") as log_file:
        log_content: str = log_file.read()
        log_lines: list[str] = log_content.split("\n")

    processed_log_content: str = ""
    for line in log_lines:
        if "Plot saved at" in line:
            image_path: str = re.search("Plot saved at (.*)", line).group(1)
            line += f"\n\n![](../{image_path})\n"
        processed_log_content += line + "\n"

    todays_date: str = datetime.date.today().strftime("%B %d, %Y")
    markdown_content: str = f"""# Analysis Results for {todays_date}

{processed_log_content}

    """

    with open(f"{output_dir}/{output_name}.md", "w") as md_file:
        md_file.write(markdown_content)


def create_pdf_report(markdown_filename: Path, output_dir: Path, font_size: str = "8pt"):
    """
    Generates a PDF report from a markdown file.
    This function converts a markdown file into a PDF file directly using pypandoc,
    preserving all formatting including code blocks and embedded images.
    The PDF file is saved in the specified `output_dir` with the same base name as the `markdown_filename`.

    Parameters
    ----------
    markdown_filename : Path
        The path to the markdown file to be converted into PDF.
    output_dir : Path
        The directory where the PDF file will be saved.
    font_size : str, optional
        The font size for the PDF (default is '8pt').
    """
    os.chdir(output_dir)

    markdown_path: Path = Path(f"{markdown_filename}.md")
    new_md_file = split_tables_in_markdown(markdown_path)
    new_md_file = split_regression_long_vars(new_md_file)
    output_pdf_path: Path = Path(f"{markdown_filename}.pdf")

    fontsize_number = font_size[:-2]

    header_includes = f"""
    \\usepackage[utf8]{{inputenc}}
    \\usepackage{{lmodern}}
    \\usepackage{{adjustbox}}
    \\usepackage{{anyfontsize}}
    \\usepackage{{geometry}}
    \\usepackage{{listings}} 
    \\lstset{{basicstyle=\\fontsize{{{fontsize_number}}}{{{int(fontsize_number) * 0.9}}}\selectfont}}
    \\fontsize{{{fontsize_number}}}{{{int(fontsize_number) * 1.2}}}\\selectfont
    """

    table_settings = f"""
    \\usepackage{{etoolbox}}
    \\BeforeBeginEnvironment{{tabular}}{{\\begin{{adjustbox}}{{max width=\\textwidth}}}}
    \\AfterEndEnvironment{{tabular}}{{\\end{{adjustbox}}}}
    """

    extra_args = [
        '--pdf-engine=xelatex',
        '--variable', f'geometry:top=1in, bottom=1in, left=1in, right=1in',
        '--variable', f'header-includes:{header_includes}',
        '--variable', f'header-includes:{table_settings}',
        '-s'
    ]

    pypandoc.convert_file(new_md_file, 'pdf', outputfile=output_pdf_path, extra_args=extra_args)

    os.remove(Path(f"{markdown_filename}_split_tables.md"))
    os.remove(Path(f"{markdown_filename}_split_tables_split_regression.md"))


def split_tables_in_markdown(file_path: str, max_width=100) -> Path:
    """
    Process a markdown file and split tables that are wider than `max_width` into smaller tables.
    """
    with open(file_path, 'r') as f:
        lines = f.readlines()

    new_lines: list = []
    table_buffer: list = []
    for line in lines:
        if line.startswith('|'):
            table_buffer.append(line.rstrip('\n'))
        else:
            if table_buffer:
                new_lines.extend(split_table_into_smaller_ones(table_buffer, max_width))
                table_buffer: list = []

            new_lines.append(line.rstrip('\n'))

    if table_buffer:
        new_lines.extend(split_table_into_smaller_ones(table_buffer, max_width))

    new_file_path = str(file_path).replace('.md', '_split_tables.md')
    with open(new_file_path, 'w') as f:
        f.write('\n'.join(new_lines))

    return Path(new_file_path)


def split_table_into_smaller_ones(table_lines, max_width: int) -> list[str]:
    """
    Given the lines of a table and a maximum width, split the table into multiple smaller tables
    """
    new_lines: list = []
    headers: str = table_lines[0].split('|')[1:-1]
    rows: list = [row.split('|')[1:-1] for row in table_lines[1:]]

    current_width: int = 0
    start_col: int = 0
    for end_col, header in enumerate(headers, start=1):
        current_width += len(header) + 3
        if current_width > max_width or end_col == len(headers):
            new_headers: str = headers[start_col:end_col]
            new_lines.append('|' + '|'.join(new_headers) + '|')
            for row in rows:
                new_cells: str = row[start_col:end_col]
                new_lines.append('|' + '|'.join(new_cells) + '|')
            new_lines.append('\n')
            current_width: int = 0
            start_col: str = end_col

    return new_lines


def split_regression_long_vars(file_path: Path, max_width: int=105) -> Path:
    """ Process a regression result file and split variables that have too long lines
    (length > `max_width`) into smaller strings. """
    with open(file_path, 'r') as f:
        lines = f.readlines()

    new_lines: list = []
    is_ols_block, is_const_met = False, False

    for line in lines:
        if 'OLS Regression Results' in line:
            is_ols_block = True
        if line.strip() == '```':
            is_ols_block = False
        if is_ols_block:
            if 'const' in line:
                is_const_met = True
            if line.strip().startswith('==='):
                is_const_met = False
            if is_const_met:
                if set(line.strip()) not in [{'-', '+', '='}, {' '}, {''}]:
                    split_line = line.split(' ')
                    temp_line = ''
                    new_line = []
                    for word in split_line:
                        if (len(temp_line + word) <= max_width or '[' in word or ']' in word) and not word.startswith('----'):
                            temp_line += ' ' + word
                        else:
                            new_line.append(temp_line.strip())
                            temp_line = word
                    if temp_line:
                        new_line.append(temp_line.strip())
                    new_lines.extend(new_line)
                else:
                    new_lines.append(line.strip()[:max_width])
            else:
                new_lines.append(line.rstrip('\n'))
        else:
            new_lines.append(line.rstrip('\n'))

    new_file_path = Path(str(file_path).replace('.md', '_split_regression.md'))
    with open(new_file_path, 'w') as f:
        f.write('\n'.join(new_lines))

    return new_file_path
