import base64
import io
import os
from pathlib import Path
from typing import Any, IO

from pypdf import PaperSize, PdfReader, PdfWriter, Transformation
from pypdf.papersizes import Dimensions

ADDED_MARGIN = 0.2
StreamType = IO[Any]
StrByteType = str | StreamType


def array_pdfs(*input_files: Path, output_dir: Path = None, print_files=False, suffix: str = "_on_a4"):
    input_files = list(input_files)
    output_dir = output_dir or input_files[0].parent / "arrayed"
    output_dir.mkdir(exist_ok=True)
    for file in input_files:
        output_file = output_dir / f"{file.stem}{suffix}.pdf"
        print(f"Processing {file.name} into {output_file}...")
        on_a4(file, output_file)
        if print_files:
            os.startfile(output_file, "print")


def get_scale_factor(input_size: Dimensions, output_size: Dimensions) -> float:
    return min([(output_size.width / input_size.height), (output_size.height / input_size.width)]) / (1 + ADDED_MARGIN)


def on_a4(
    input_file: Path,
    output_file: Path,
    output_size=PaperSize.A4,
):
    if not input_file.is_file():
        raise ValueError(f"Invalid input file path {input_file} of type {type(input_file)}.")
    if output_file.suffix != ".pdf":
        raise ValueError("Ouput path not .pdf.")
    reader = PdfReader(input_file)
    do_resize(reader, output_file, output_size)


def on_a42(
    input_stream: StrByteType | Path,
    output_file: Path,
    output_size: Dimensions = PaperSize.A4,
):
    # if not input_stream.is_file():
    #     raise ValueError(f'Invalid input file path {input_stream} of type {type(input_stream)}.')
    if output_file.suffix != ".pdf":
        output_file = output_file.with_suffix(".pdf")
    reader = PdfReader(input_stream)
    do_resize(reader, output_file, output_size)


def do_resize(reader: PdfReader, output_path: Path, output_size: Dimensions = PaperSize.A4):
    input_size = Dimensions(reader.pages[0].mediabox.width, reader.pages[0].mediabox.height)
    scale_factor = get_scale_factor(input_size, output_size)
    resized = Dimensions(int(scale_factor * input_size.width), int(scale_factor * input_size.height))

    left_translation, right_translation = get_translations(resized, output_size)

    writer = PdfWriter()

    for i in range(0, len(reader.pages), 2):
        # width and height are swapped for landscape
        d_page = writer.add_blank_page(output_size.height, output_size.width)
        for j in range(2):
            if i + j < len(reader.pages):
                page_num = i + j
                translate_ = (
                    left_translation if j == 0 else right_translation
                )  # zero-indexed so swap left right vs even odd
                page = reader.pages[page_num]
                page.scale_by(scale_factor)
                d_page.merge_transformed_page(page, translate_)

    with open(output_path, "wb") as out_pdf_file:
        writer.write(out_pdf_file)


def get_translations(in_size: Dimensions, out_size: Dimensions) -> tuple[Transformation, Transformation]:
    x_left, x_right, y = get_translation_dims(in_size, out_size)
    return (Transformation().translate(x_left, y), Transformation().translate(x_right, y))


#
def get_translation_dims(in_size: Dimensions, out_size: Dimensions) -> tuple[float, float, float]:
    translate_x_left = (out_size.height / 2 - in_size.width) / 2
    translate_x_right = (out_size.height / 2 - in_size.width) / 2 + out_size.height / 2
    translate_y = (out_size.width - in_size.height) / 2

    return translate_x_left, translate_x_right, translate_y


async def array_write_content(content: bytes, output: Path):
    og_size_path = output.parent / "original_size" / output.name
    og_size_path.parent.mkdir(parents=True, exist_ok=True)
    og_size_path.write_bytes(content)
    print(f"Resizing {og_size_path} to A4 at {output}")
    on_a4(input_file=og_size_path, output_file=output)
    print(f"Wrote label to {output}")


def merge_bytes_string_pdfs(pdf_strings: list[str]) -> io.BytesIO:
    writer = PdfWriter()

    for label in pdf_strings:
        decoded = base64.b64decode(label)
        reader = PdfReader(io.BytesIO(decoded))
        for page in reader.pages:
            writer.add_page(page)

    merged_stream = io.BytesIO()
    writer.write(merged_stream)
    merged_stream.seek(0)
    return merged_stream


def merge_array_bytes_string_pdfs(pdf_strings: list[str], output_path: Path):
    merged_stream = merge_bytes_string_pdfs(pdf_strings)
    on_a42(
        input_stream=merged_stream,
        output_file=output_path,
    )
