import re
from datetime import datetime
from pathlib import Path

from mneia_isbn import ISBN

_SSID_PATTERN = re.compile(r"(?<![0-9a-zA-Z])\d{8}(?![0-9a-zA-Z])")
_SSID_FORBIDDEN_PREFIX_PATTERN = re.compile(r"^(1[7-9]|2\d|7\d)\d{6}$")
_ISBN_CANDIDATE_PATTERN = re.compile(r"(?<!\d)(\d{9}[0-9Xx]|\d{13})(?!\d)")
_ISBN_SEPARATOR_PATTERN = re.compile(r"[-·\s]")
_MULTI_SPACE_PATTERN = re.compile(r"\s+")
_TITLE_EXT_PATTERN = re.compile(
    r"\.(pdf|epub|mobi|txt|docx|doc|fb2|djvu|tar.gz|rar|zip|uvz|azw3|7z|pdz|caj)$",
    flags=re.IGNORECASE,
)
_YEAR_ONLY_PATTERN = re.compile(r"^\s*(\d{4})\s*(?:年)?\s*$")
_YEAR_MONTH_ONLY_PATTERN = re.compile(r"^\s*(\d{4})\s*(?:年|[\-\./])\s*(\d{1,2})\s*(?:月)?\s*$")
_DATE_PARSE_FORMATS = (
    "%Y%m%d",
    "%Y-%m-%d",
    "%Y-%m-%d %H:%M",
    "%Y-%m-%d %H:%M:%S",
    "%Y/%m/%d",
    "%Y/%m/%d %H:%M",
    "%Y/%m/%d %H:%M:%S",
    "%Y.%m.%d",
    "%Y.%m.%d %H:%M",
    "%Y.%m.%d %H:%M:%S",
)


def _try_parse_datetime(text: str) -> datetime | None:
    text = text.strip()
    if not text:
        return None

    candidates = [
        text,
        text.replace("T", " "),
        text.replace("Z", "+00:00"),
        text.replace("T", " ").replace("Z", "+00:00"),
        text.replace("年", "-").replace("月", "").replace("日", ""),
        text.replace("年", "-").replace("月", "-").replace("日", ""),
    ]

    seen: set[str] = set()
    normalized_candidates: list[str] = []
    for candidate in candidates:
        candidate = candidate.strip()
        if not candidate or candidate in seen:
            continue
        seen.add(candidate)
        normalized_candidates.append(candidate)

    for candidate in normalized_candidates:
        try:
            return datetime.fromisoformat(candidate)
        except ValueError:
            continue

    for candidate in normalized_candidates:
        try:
            for fmt in _DATE_PARSE_FORMATS:
                try:
                    return datetime.strptime(candidate, fmt)
                except ValueError:
                    continue
        except ValueError:
            continue

    return None


def is_valid_date(text: str) -> bool:
    """
    判断 8 位数字是否可解释为有效日期。

    支持两种形式：
    - YYYYMMDD
    - YYYYDDMM（兼容少见写法）
    """
    if len(text) != 8:
        return False

    try:
        year = int(text[:4])
        month = int(text[4:6])
        day = int(text[6:8])
        if 1700 <= year <= 2950 and 1 <= month <= 12 and 1 <= day <= 31:
            datetime(year, month, day)
            return True
    except ValueError:
        pass

    try:
        year = int(text[:4])
        day = int(text[4:6])
        month = int(text[6:8])
        if 1900 <= year <= 2100 and 1 <= month <= 12 and 1 <= day <= 31:
            datetime(year, month, day)
            return True
    except ValueError:
        pass

    return False


def extract_year_month(text: str) -> str:
    """
    将日期相关字符串规范化为 YYYY.MM 或 YYYY。

    规则：
    - 形如 YYYY年MM月DD日 / YYYY-MM-DD / YYYY/MM/DD / YYYY.MM.DD / YYYYMMDD -> YYYY.MM
    - 支持日期后带时间，如 YYYY-MM-DD HH:MM[:SS] -> YYYY.MM
    - 形如 YYYY 或 YYYY年 -> YYYY
    - 月份不足两位时补零
    - 输入无法识别时返回空字符串
    """
    if not text or not isinstance(text, str):
        return ""

    text = text.strip()

    match = _YEAR_ONLY_PATTERN.fullmatch(text)
    if match:
        return match.group(1)

    match = _YEAR_MONTH_ONLY_PATTERN.fullmatch(text)
    if match:
        year = int(match.group(1))
        month = int(match.group(2))
        if not 1 <= month <= 12:
            return ""
        return f"{year:04d}.{month:02d}"

    parsed_dt = _try_parse_datetime(text)
    if parsed_dt is None:
        return ""

    return f"{parsed_dt.year:04d}.{parsed_dt.month:02d}"


def extract_yyyymm(text: str) -> str:
    """
    `extract_year_month` 的等价别名。

    返回值与 `extract_year_month` 完全一致。
    """
    return extract_year_month(text)


def extract_ssid(text: str | int) -> int | None:
    """
    从文本中提取第一个 SSID（8 位数字）。

    规则：
    - 必须是独立数字块（前后不能紧邻字母或数字）
    - 排除可识别为日期的 8 位数字
    - 首位不能为 0
    - 前两位不能在 17~29 之间（即排除 17/18/19 和所有 2 开头）
    - 不能以 7 开头

    返回：
    - 提取成功：SSID 整数
    - 提取失败：None
    """
    if isinstance(text, int):
        text_str = str(text)
        if 10000000 <= text <= 99999999 and not is_valid_date(text_str) and not _SSID_FORBIDDEN_PREFIX_PATTERN.fullmatch(text_str):
            return text
        return None

    if not text or not isinstance(text, str):
        return None

    for match in _SSID_PATTERN.findall(text):
        if not is_valid_date(match) and len(match) == 8 and match[0] != "0" and not _SSID_FORBIDDEN_PREFIX_PATTERN.fullmatch(match):
            return int(match)

    return None


def extract_isbn(text: str) -> str:
    """
    从文本中提取第一个有效 ISBN，并统一返回 ISBN-13。

    支持：
    - ISBN-10（末位可为 X/x）
    - ISBN-13
    - 文本中含空格、短横线、间隔符（会先清理）
    """
    if not text or not isinstance(text, str):
        return ""

    clean_text = _ISBN_SEPARATOR_PATTERN.sub("", text)

    for candidate in _ISBN_CANDIDATE_PATTERN.findall(clean_text):
        try:
            if candidate[-1] == "x":
                candidate = candidate[:-1] + "X"

            bn = ISBN(candidate)
            if bn.is_valid:
                return bn.as_isbn13

            if len(candidate) == 10:
                bn3 = ISBN("978" + candidate)
                if bn3.is_valid:
                    return bn3.as_isbn13
                continue

            if candidate.startswith("978") and len(candidate) == 13:
                bn2 = ISBN(candidate[3:])
                if bn2.is_valid:
                    return bn2.as_isbn13
                continue

            if candidate.startswith("987") and len(candidate) == 13:
                bn3 = ISBN(f"978{candidate[3:]}")
                if bn3.is_valid:
                    return bn3.as_isbn13
                continue
        except Exception:
            pass

    return ""


def extract_filetype(filename: str | Path) -> str | None:
    """
    从文件名提取合法扩展名并返回小写。

    规则：
    - 只取最后一段后缀（如 tar.gz 返回 gz）
    - 仅允许字母和数字
    - 长度需小于 5
    """
    suffix = Path(filename).suffix
    if not suffix:
        return None

    ext = suffix[1:].lower()
    if len(ext) >= 5:
        return None

    if re.fullmatch(r"[A-Za-z0-9]+", ext):
        return ext.lower()

    return None


def is_ssid_or_isbn(text: str) -> bool:
    """
    判断文本是否包含可识别的 SSID 或 ISBN。
    """
    return bool(extract_ssid(text) or extract_isbn(text))


def normalize_title(title: str) -> str:
    """
    规范化标题文本。

    处理步骤：
    - 合并多余空白
    - 去掉常见电子书扩展名
    - 移除可识别的 SSID / ISBN
    - 将下划线和竖线标准化为空格
    """
    title = title.strip()
    title = _MULTI_SPACE_PATTERN.sub(" ", title)
    title = _TITLE_EXT_PATTERN.sub("", title)

    ssid = extract_ssid(title)
    if ssid is not None:
        title = title.replace(str(ssid), "")

    isbn = extract_isbn(title)
    if isbn:
        title1 = title.replace(isbn, "")
        if title1 != title:
            title = title1
        else:
            title1 = re.sub(r"[-—一·\s]", "", title)
            title1 = title1.replace(isbn, "")
            title = title1

    title = title.replace("_", " ").replace("│", " ")
    title = _MULTI_SPACE_PATTERN.sub(" ", title).strip()

    return title
