from __future__ import annotations

from dataclasses import dataclass


@dataclass
class MatrixCipherResult:
    """保存一次矩阵置换密码运算的完整结果。"""

    original_text: str
    normalized_text: str
    key: str
    permutation: list[int]
    source_rows: list[list[str]]
    target_rows: list[list[str]]
    result_text: str


def shift_english_letter(char: str, shift: int) -> str:
    """
    将单个英文字母循环移动 shift 位。

    说明：
    1. 只处理 A-Z / a-z
    2. 其他字符直接原样返回
    3. 使用模 26 运算保证越界后回到字母表开头
    """
    if "a" <= char <= "z":
        base = ord("a")
        return chr((ord(char) - base + shift) % 26 + base)

    if "A" <= char <= "Z":
        base = ord("A")
        return chr((ord(char) - base + shift) % 26 + base)

    return char


def caesar_encrypt(text: str, key: int) -> str:
    """使用凯撒密码加密。"""
    return "".join(shift_english_letter(char, key) for char in text)


def caesar_decrypt(text: str, key: int) -> str:
    """使用凯撒密码解密。"""
    return caesar_encrypt(text, -key)


def build_caesar_report(text: str, key: int, result: str, mode: str) -> str:
    """把凯撒密码的结果整理成适合终端展示的中文文本。"""
    mode_label = "加密" if mode == "encrypt" else "解密"
    input_label = "明文" if mode == "encrypt" else "密文"
    output_label = "密文" if mode == "encrypt" else "明文"

    lines = [
        f"操作：凯撒密码{mode_label}",
        f"{input_label}：{text}",
        f"密钥 k：{key}",
        f"{output_label}：{result}",
        "说明：程序只移动英文字母，其他字符保持不变。",
    ]
    return "\n".join(lines)


def remove_whitespace(text: str) -> str:
    """去掉字符串中的所有空白字符。"""
    return "".join(char for char in text if not char.isspace())


def build_permutation_from_key(key: str) -> list[int]:
    """
    根据密钥生成题目示例风格的置换序列。

    例如：
    key = "cipher"
    返回 [1, 4, 5, 3, 2, 6]

    做法是：
    1. 先按字母表顺序对密钥字符排序
    2. 再把排序后的名次写回原位置
    """
    indexed_key = list(enumerate(key))
    sorted_key = sorted(indexed_key, key=lambda item: (item[1].casefold(), item[0]))

    permutation = [0] * len(key)
    for rank, (original_index, _) in enumerate(sorted_key, start=1):
        permutation[original_index] = rank

    return permutation


def split_rows(text: str, column_count: int) -> list[list[str]]:
    """把字符串按固定列数切分成多行。"""
    return [list(text[index : index + column_count]) for index in range(0, len(text), column_count)]


def reorder_row_by_permutation(row: list[str], permutation: list[int]) -> list[str]:
    """
    按题目示例给出的置换序列重排一行字符。

    例如 permutation 为 [1, 4, 5, 3, 2, 6] 时，
    就按第 1、4、5、3、2、6 列的顺序取字符。

    如果最后一行长度不够，就只处理实际存在的列。
    """
    reordered_row: list[str] = []
    for column_number in permutation:
        column_index = column_number - 1
        if column_index < len(row):
            reordered_row.append(row[column_index])
    return reordered_row


def restore_row_from_permutation(row: list[str], permutation: list[int], row_length: int) -> list[str]:
    """
    根据置换序列把一行密文还原回原始顺序。

    还原思路是：
    1. 先找出当前这一行实际使用了哪些列
    2. 再把密文中的字符放回它们原来所在的位置
    """
    restored_row = [""] * row_length
    used_columns = [column_number for column_number in permutation if column_number <= row_length]

    for char, column_number in zip(row, used_columns):
        restored_row[column_number - 1] = char

    return restored_row


def matrix_encrypt(plaintext: str, key: str) -> MatrixCipherResult:
    """使用矩阵置换密码加密。"""
    normalized_text = remove_whitespace(plaintext)
    normalized_key = remove_whitespace(key)

    if not normalized_text:
        raise ValueError("明文去掉空白后不能为空。")
    if not normalized_key:
        raise ValueError("密钥去掉空白后不能为空。")

    permutation = build_permutation_from_key(normalized_key)
    source_rows = split_rows(normalized_text, len(normalized_key))
    target_rows = [reorder_row_by_permutation(row, permutation) for row in source_rows]
    result_text = "".join("".join(row) for row in target_rows)

    return MatrixCipherResult(
        original_text=plaintext,
        normalized_text=normalized_text,
        key=normalized_key,
        permutation=permutation,
        source_rows=source_rows,
        target_rows=target_rows,
        result_text=result_text,
    )


def matrix_decrypt(ciphertext: str, key: str) -> MatrixCipherResult:
    """使用矩阵置换密码解密。"""
    normalized_text = remove_whitespace(ciphertext)
    normalized_key = remove_whitespace(key)

    if not normalized_text:
        raise ValueError("密文去掉空白后不能为空。")
    if not normalized_key:
        raise ValueError("密钥去掉空白后不能为空。")

    permutation = build_permutation_from_key(normalized_key)
    source_rows = split_rows(normalized_text, len(normalized_key))
    target_rows = [
        restore_row_from_permutation(row, permutation, len(row)) for row in source_rows
    ]
    result_text = "".join("".join(row) for row in target_rows)

    return MatrixCipherResult(
        original_text=ciphertext,
        normalized_text=normalized_text,
        key=normalized_key,
        permutation=permutation,
        source_rows=source_rows,
        target_rows=target_rows,
        result_text=result_text,
    )


def format_matrix(rows: list[list[str]], column_count: int) -> str:
    """
    把矩阵格式化成适合终端阅读的文本。

    用 . 表示当前行不存在的空位，便于观察最后一行是否填满。
    """
    if not rows:
        return "(空矩阵)"

    lines = []
    header = "列号: " + " ".join(f"{index:>2}" for index in range(1, column_count + 1))
    lines.append(header)

    for row_number, row in enumerate(rows, start=1):
        padded_row = row + ["."] * (column_count - len(row))
        lines.append(f"第{row_number}行: " + " ".join(f"{char:>2}" for char in padded_row))

    return "\n".join(lines)


def build_matrix_report(result: MatrixCipherResult, mode: str) -> str:
    """把矩阵置换密码的过程整理成易读的中文文本。"""
    input_label = "明文" if mode == "encrypt" else "密文"
    output_label = "密文" if mode == "encrypt" else "明文"
    source_title = "加密前矩阵" if mode == "encrypt" else "解密时的密文矩阵"
    target_title = "按置换重排后的矩阵" if mode == "encrypt" else "还原后的明文矩阵"

    lines = [
        f"操作：矩阵置换密码{'加密' if mode == 'encrypt' else '解密'}",
        f"{input_label}（原始输入）：{result.original_text}",
    ]

    if result.original_text != result.normalized_text:
        lines.append(f"{input_label}（去掉空白后）：{result.normalized_text}")

    lines.extend(
        [
            f"密钥：{result.key}",
            "密钥字符: " + " ".join(result.key),
            "置换序列: " + " ".join(str(number) for number in result.permutation),
            f"{source_title}：",
            format_matrix(result.source_rows, len(result.key)),
            f"{target_title}：",
            format_matrix(result.target_rows, len(result.key)),
            f"{output_label}：{result.result_text}",
        ]
    )

    return "\n".join(lines)
