from __future__ import annotations

import re
from typing import List, Sequence, Tuple


KEY_TOKEN_RE = r"[A-Za-z0-9+/=_-]{16,}"


def is_hex_id(token: str) -> bool:
    """判断 token 是否像云资源 ID，避免把资源 ID 误当成 AK/SK。"""
    return bool(re.fullmatch(r"[0-9a-fA-F]{24,}", token or ""))


def clean_key_token(token: str) -> str:
    """清理复制密钥时常见的中文标点、括号和引号。"""
    return (token or "").strip().strip("：:，,;；'\"<>[](){}")


def _clean_mark_token(token: str) -> str:
    """清理用户备注 Mark，保留账号名常见的字母、数字、点、下划线和横线。"""
    value = (token or "").strip().strip("：:，,;；'\"<>[](){}")
    value = re.sub(r"\s+", "", value)
    return value[:80]


def _looks_like_mark(token: str) -> bool:
    """判断文本是否适合作为 Mark，避免把 AK/SK、资源 ID 或页面菜单识别进去。"""
    value = _clean_mark_token(token)
    if not value:
        return False
    lower = value.lower()
    blocked = {
        "logo",
        "控制台",
        "费用",
        "资源",
        "企业",
        "工单",
        "备案",
        "支持",
        "合作",
        "我的accesskey",
        "accesskey",
        "securitykey",
        "secretkey",
        "访问密钥id",
        "下载访问密钥",
        "暂无数据",
        "flexus",
        "ecs",
        "eip",
        "vpc",
        "ims",
        "hss",
        "obs",
        "rfs",
        "bms",
        "evs",
        "cbr",
        "elb",
        "rds",
        "domains",
        "apaas",
        "iam",
    }
    if lower in blocked or value in blocked:
        return False
    if len(value) < 4 or len(value) > 40:
        return False
    upper = value.upper()
    if upper.startswith(("HPUA", "AKLT")):
        return False
    if is_hex_id(value) or re.fullmatch(r"[a-fA-F0-9]{24,}", value):
        return False
    return bool(re.fullmatch(r"[A-Za-z][A-Za-z0-9._@-]{3,39}", value))


def tokens_after_label(raw: str, label_patterns: Sequence[str]) -> List[str]:
    """提取某些标签后面的疑似密钥 token。"""
    tokens: List[str] = []
    for pattern in label_patterns:
        for match in re.finditer(pattern, raw, flags=re.IGNORECASE):
            tail = raw[match.end() : match.end() + 500]
            tokens.extend(clean_key_token(token) for token in re.findall(KEY_TOKEN_RE, tail))
    return tokens


def _value_after_label_lines(raw: str, label_patterns: Sequence[str], *, exclude: Sequence[str] = ()) -> str:
    """按控制台表单标签逐行取 AK/SK，避免页面上方重复 AK 抢占 SecurityKey。"""
    excluded = {clean_key_token(value) for value in exclude if value}
    lines = [line.strip() for line in (raw or "").splitlines() if line.strip()]
    for index, line in enumerate(lines):
        for pattern in label_patterns:
            match = re.search(pattern, line, flags=re.IGNORECASE)
            if not match:
                continue
            same_line = line[match.end() :]
            for token in re.findall(KEY_TOKEN_RE, same_line):
                value = clean_key_token(token)
                if value and value not in excluded:
                    return value
            for next_line in lines[index + 1 : index + 8]:
                # 下一行已经是另一个标签时停止，防止把 SecurityKey 的值反填给 AccessKey。
                if re.search(r"Access\s*Key\s*ID|AccessKey\s*ID|AccessKeyID|Security\s*Key|SecurityKey|Secret\s*Key|SecretKey", next_line, flags=re.IGNORECASE):
                    break
                for token in re.findall(KEY_TOKEN_RE, next_line):
                    value = clean_key_token(token)
                    if value and value not in excluded:
                        return value
    return ""


def _first_mark_after_labels(lines: List[str], labels: Sequence[str]) -> str:
    """从某些标签的同一行或后几行读取 Mark。"""
    for index, line in enumerate(lines):
        normalized = line.replace(" ", "")
        for label in labels:
            if label not in normalized:
                continue
            same_line = line.split(label, 1)[-1] if label in line else ""
            for token in re.findall(r"[A-Za-z][A-Za-z0-9._@-]{3,39}", same_line):
                if _looks_like_mark(token):
                    return _clean_mark_token(token)
            for next_line in lines[index + 1 : index + 5]:
                for token in re.findall(r"[A-Za-z][A-Za-z0-9._@-]{3,39}", next_line):
                    if _looks_like_mark(token):
                        return _clean_mark_token(token)
    return ""


def _explicit_mark_after_labels(lines: List[str], labels: Sequence[str]) -> str:
    """显式 Mark 支持中文、# 等备注字符，避免客户名被截成英文片段。"""
    def usable(value: str) -> bool:
        upper = value.upper()
        lower = value.lower()
        return bool(value) and not upper.startswith(("AK", "SK")) and lower not in {"user", "mark", "markuser"}

    for index, line in enumerate(lines):
        normalized = line.replace(" ", "")
        for label in labels:
            if label not in normalized:
                continue
            same_line = line.split(label, 1)[-1] if label in line else ""
            value = _clean_mark_token(same_line.lstrip("：:= "))
            if usable(value):
                return value
            for next_line in lines[index + 1 : index + 4]:
                value = _clean_mark_token(next_line)
                if usable(value):
                    return value
    return ""


def _repeated_mark_candidate(lines: List[str]) -> str:
    """华为控制台字段异常时，从重复出现的短账号名里兜底识别 Mark。"""
    counts: dict[str, int] = {}
    last_index: dict[str, int] = {}
    for index, line in enumerate(lines):
        for token in re.findall(r"[A-Za-z][A-Za-z0-9._@-]{3,39}", line):
            value = _clean_mark_token(token)
            if not _looks_like_mark(value):
                continue
            key = value.lower()
            counts[key] = counts.get(key, 0) + 1
            last_index[key] = index
    repeated = [key for key, count in counts.items() if count >= 2]
    if not repeated:
        return ""
    # 账号名通常在导航和“我的凭证/IAM用户名/账号名”区域重复出现，取最后一次出现最靠后的候选。
    repeated.sort(key=lambda key: (counts[key], last_index[key]), reverse=True)
    chosen = repeated[0]
    for index in range(last_index[chosen], -1, -1):
        for token in re.findall(r"[A-Za-z][A-Za-z0-9._@-]{3,39}", lines[index]):
            value = _clean_mark_token(token)
            if value.lower() == chosen:
                return value
    return chosen


def _leading_mark_before_keys(lines: List[str]) -> str:
    """支持用户直接粘贴“用户名 + AK + SK”三行内容。"""
    if len(lines) < 3:
        return ""
    key_count = 0
    for line in lines[1:6]:
        if re.search(KEY_TOKEN_RE, line):
            key_count += 1
    if key_count < 2:
        return ""
    first = _clean_mark_token(lines[0])
    return first if _looks_like_mark(first) else ""


def extract_mark(raw: str, cloud: str = "") -> str:
    """从云控制台复制文本里识别用户 Mark，用于复制结果尾部追加账号备注。"""
    raw = raw or ""
    lines = [line.strip() for line in raw.splitlines() if line.strip()]
    explicit = _explicit_mark_after_labels(lines, ("Mark", "MARK", "备注", "标记"))
    if explicit:
        return explicit
    leading = _leading_mark_before_keys(lines)
    if leading:
        return leading

    cloud_key = (cloud or "").strip().lower()
    if cloud_key in {"huawei", "huaweicloud", "华为", "华为云"}:
        # 华为控制台页面里会出现“统一身份认证服务 IAM”和 Flexus 等服务名，
        # 不能用泛化的 IAM 作为锚点，否则会把服务名误识别成 Mark。
        value = _first_mark_after_labels(lines, ("IAM用户名", "账号名", "用户名"))
        if value:
            return value
        value = _repeated_mark_candidate(lines)
        if value:
            return value

    if cloud_key in {"ctyun", "天翼", "天翼云"}:
        for match in re.finditer(r"([A-Za-z][A-Za-z0-9._@-]{3,39})\s*我的\s*AccessKey", raw, flags=re.IGNORECASE):
            value = _clean_mark_token(match.group(1))
            if _looks_like_mark(value):
                return value
        for index, line in enumerate(lines):
            compact_line = line.replace(" ", "")
            if "我的AccessKey" not in compact_line and "AccessKey" not in compact_line:
                continue
            for prev in reversed(lines[max(0, index - 8) : index]):
                tokens = re.findall(r"[A-Za-z][A-Za-z0-9._@-]{3,39}", prev)
                for token in reversed(tokens):
                    if _looks_like_mark(token):
                        return _clean_mark_token(token)

    return _first_mark_after_labels(lines, ("IAM用户名", "账号名", "用户名"))


def append_mark_to_output(text: str, mark: str) -> str:
    """复制 L2TP/Socks5 时把 Mark 作为最后一行追加，空 Mark 不改变原文。"""
    output = (text or "").strip()
    mark_value = _clean_mark_token(mark)
    if not output or not mark_value:
        return output
    lines = output.splitlines()
    if lines and lines[-1].strip() == mark_value:
        return output
    return f"{output}\n{mark_value}"


def extract_ak_sk(raw: str) -> Tuple[str, str]:
    """从用户复制的云账号密钥文本中尽量解析 AK 和 SK。"""
    raw = raw or ""
    tokens = [clean_key_token(token) for token in re.findall(KEY_TOKEN_RE, raw)]
    if len(tokens) < 2:
        return "", ""

    ak_label_patterns = (
        r"Access\s*Key\s*ID",
        r"AccessKeyID",
        r"AccessKey\s*ID",
        r"Access\s*Key\s*Id",
        r"访问密钥\s*AK",
        r"访问密钥\s*ID",
        r"璁块棶瀵嗛挜\s*AK",
        r"璁块棶瀵嗛挜\s*ID",
        r"\bAK\b",
    )
    sk_label_patterns = (
        r"Security\s*Key",
        r"SecurityKey",
        r"Secret\s*Access\s*Key",
        r"SecretAccessKey",
        r"Secret\s*Key",
        r"SecretKey",
        r"访问密钥\s*SK",
        r"密钥\s*SK",
        r"璁块棶瀵嗛挜\s*SK",
        r"瀵嗛挜\s*SK",
        r"瀵嗛挜",
        r"\bSK\b",
    )

    def looks_like_ak(token: str) -> bool:
        upper = token.upper()
        return (
            upper.startswith("HPUA")
            or upper.startswith("AKLT")
            or (16 <= len(token) <= 128 and "=" not in token and not is_hex_id(token))
        )

    def looks_like_sk(token: str, ak_value: str = "") -> bool:
        upper = token.upper()
        return (
            token != ak_value
            and len(token) >= 24
            and not upper.startswith("HPUA")
            and not upper.startswith("AKLT")
            and not is_hex_id(token)
        )

    secret_label_regex = (
        r"Security\s*Key|SecurityKey|Secret\s*Access\s*Key|SecretAccessKey|Secret\s*Key|SecretKey|"
        r"访问密钥\s*SK|密钥\s*SK|密钥"
    )
    for secret_match in reversed(list(re.finditer(secret_label_regex, raw, flags=re.IGNORECASE))):
        secret_tail = raw[secret_match.end() : secret_match.end() + 300]
        scoped_sk_tokens = [clean_key_token(token) for token in re.findall(KEY_TOKEN_RE, secret_tail)]
        scoped_sk = next((token for token in scoped_sk_tokens if token and len(token) >= 16), "")
        if not scoped_sk:
            continue

        head = raw[: secret_match.start()]
        anchors = [
            head.rfind("下载访问密钥"),
            head.rfind("新建密钥成功"),
            head.rfind("查看密钥详情"),
            head.rfind("访问密钥ID"),
            head.rfind("AccessKeyID"),
            head.rfind("Access Key ID"),
        ]
        anchor = max(anchors)
        scoped_head = head[anchor:] if anchor >= 0 else head[-900:]
        ak_label_matches = list(re.finditer("|".join(ak_label_patterns[:-1]), scoped_head, flags=re.IGNORECASE))
        scoped_ak_text = scoped_head[ak_label_matches[-1].end() :] if ak_label_matches else scoped_head
        scoped_ak_tokens = [clean_key_token(token) for token in re.findall(KEY_TOKEN_RE, scoped_ak_text)]
        scoped_ak_candidates = [
            token
            for token in scoped_ak_tokens
            if token != scoped_sk and (looks_like_ak(token) or is_hex_id(token))
        ]
        scoped_ak = next((token for token in reversed(scoped_ak_candidates) if token.upper().startswith(("HPUA", "AKLT"))), "")
        if not scoped_ak and scoped_ak_candidates:
            scoped_ak = scoped_ak_candidates[-1]
        if scoped_ak:
            return scoped_ak, scoped_sk

    explicit_ak = _value_after_label_lines(raw, ak_label_patterns)
    explicit_sk = _value_after_label_lines(raw, sk_label_patterns, exclude=(explicit_ak,))
    if explicit_ak and explicit_sk:
        return explicit_ak, explicit_sk

    ak_tokens = tokens_after_label(raw, ak_label_patterns)
    sk_tokens = tokens_after_label(raw, sk_label_patterns)
    ak_candidates = [token for token in ak_tokens + tokens if looks_like_ak(token)]
    ak = next((token for token in ak_candidates if token.upper().startswith(("HPUA", "AKLT"))), "")
    if not ak and ak_candidates:
        ak = ak_candidates[0]

    sk_candidates = [token for token in sk_tokens + tokens if looks_like_sk(token, ak)]
    sk = next((token for token in sk_candidates if len(token) >= 32), "")
    if not sk:
        sk = next((token for token in sk_candidates), "")
    return ak, sk
