from __future__ import annotations

from dataclasses import dataclass
from datetime import date, datetime
from email.utils import parseaddr
from io import BytesIO
from typing import Any

from openpyxl import Workbook, load_workbook

EMPLOYEE_SHEET_NAME = "员工目录"
INSTRUCTION_SHEET_NAME = "填表说明"

EMPLOYEE_HEADERS: tuple[str, ...] = (
    "员工编号*",
    "姓名*",
    "邮箱*",
    "性别",
    "出生日期",
    "手机号",
    "入职日期",
    "办公地点",
    "岗位*",
    "职级*",
    "部门编码",
    "直属上级工号",
    "财务归口",
    "成本中心",
    "银行户名",
    "开户行",
    "银行账号",
    "在职状态*",
    "角色编码",
)

HEADER_TO_FIELD: dict[str, str] = {
    "员工编号*": "employee_no",
    "姓名*": "name",
    "邮箱*": "email",
    "性别": "gender",
    "出生日期": "birth_date",
    "手机号": "phone",
    "入职日期": "join_date",
    "办公地点": "location",
    "岗位*": "position",
    "职级*": "grade",
    "部门编码": "organization_unit_code",
    "直属上级工号": "manager_employee_no",
    "财务归口": "finance_owner_name",
    "成本中心": "cost_center",
    "银行户名": "bank_account_name",
    "开户行": "bank_name",
    "银行账号": "bank_account_no",
    "在职状态*": "employment_status",
    "角色编码": "role_codes",
}

VALID_EMPLOYMENT_STATUSES = {"在职", "试用中", "停用"}
DEFAULT_ROLE_CODES = ("user",)
MAX_IMPORT_ROWS = 2000
MAX_IMPORT_BYTES = 5 * 1024 * 1024


@dataclass(frozen=True)
class EmployeeImportRow:
    row_number: int
    employee_no: str
    name: str
    email: str
    gender: str | None
    birth_date: date | None
    phone: str | None
    join_date: date | None
    location: str | None
    position: str
    grade: str
    organization_unit_code: str | None
    manager_employee_no: str | None
    finance_owner_name: str | None
    cost_center: str | None
    bank_account_name: str | None
    bank_name: str | None
    bank_account_no: str | None
    employment_status: str
    role_codes: list[str]


@dataclass(frozen=True)
class EmployeeSpreadsheetError:
    row: int
    column: str
    employee_no: str
    message: str


def build_import_template_bytes() -> bytes:
    workbook = Workbook()
    sheet = workbook.active
    sheet.title = EMPLOYEE_SHEET_NAME
    sheet.append(list(EMPLOYEE_HEADERS))

    instructions = workbook.create_sheet(INSTRUCTION_SHEET_NAME)
    instructions.append(["字段", "说明"])
    instruction_rows = [
        ("员工编号*", "必填，全局唯一，导入时用于判断新建或覆盖。"),
        ("姓名*", "必填。"),
        ("邮箱*", "必填，全局唯一。"),
        ("性别", "可选：男、女，留空表示不填写。"),
        ("出生日期", "可选，格式 YYYY-MM-DD。"),
        ("手机号", "可选。"),
        ("入职日期", "可选，格式 YYYY-MM-DD。"),
        ("办公地点", "可选。"),
        ("岗位*", "必填。"),
        ("职级*", "必填，例如 P3、P5。"),
        ("部门编码", "可选，须与系统组织编码一致，例如 FINANCE-DEPT。"),
        ("直属上级工号", "可选，须为系统中已有员工编号，或出现在本次导入表中。"),
        ("财务归口", "可选。"),
        ("成本中心", "可选。"),
        ("银行户名", "可选，留空时默认使用员工姓名。"),
        ("开户行", "可选，留空时使用系统默认演示开户行。"),
        ("银行账号", "可选，留空时系统按员工编号生成演示账号。"),
        ("在职状态*", "必填：在职、试用中、停用。"),
        ("角色编码", "可选，多个角色用英文逗号分隔，例如 user,finance；留空默认为 user。"),
        ("导入规则", "全部校验通过后才写入数据库；任一行有错则整批不导入，原有数据保持不变。"),
    ]
    for row in instruction_rows:
        instructions.append(list(row))

    buffer = BytesIO()
    workbook.save(buffer)
    return buffer.getvalue()


def build_export_workbook_bytes(rows: list[list[Any]]) -> bytes:
    workbook = Workbook()
    sheet = workbook.active
    sheet.title = EMPLOYEE_SHEET_NAME
    sheet.append(list(EMPLOYEE_HEADERS))
    for row in rows:
        sheet.append(row)

    buffer = BytesIO()
    workbook.save(buffer)
    return buffer.getvalue()


def parse_employee_workbook(content: bytes) -> tuple[list[EmployeeImportRow], list[EmployeeSpreadsheetError]]:
    errors: list[EmployeeSpreadsheetError] = []

    if not content:
        return [], [
            EmployeeSpreadsheetError(
                row=0,
                column="文件",
                employee_no="",
                message="上传文件不能为空。",
            )
        ]

    if len(content) > MAX_IMPORT_BYTES:
        return [], [
            EmployeeSpreadsheetError(
                row=0,
                column="文件",
                employee_no="",
                message=f"文件大小不能超过 {MAX_IMPORT_BYTES // (1024 * 1024)}MB。",
            )
        ]

    try:
        workbook = load_workbook(filename=BytesIO(content), read_only=True, data_only=True)
    except Exception:
        return [], [
            EmployeeSpreadsheetError(
                row=0,
                column="文件",
                employee_no="",
                message="无法解析 Excel 文件，请使用系统提供的 .xlsx 模板。",
            )
        ]

    if EMPLOYEE_SHEET_NAME not in workbook.sheetnames:
        return [], [
            EmployeeSpreadsheetError(
                row=0,
                column="工作表",
                employee_no="",
                message=f"缺少工作表“{EMPLOYEE_SHEET_NAME}”。",
            )
        ]

    worksheet = workbook[EMPLOYEE_SHEET_NAME]
    raw_rows = list(worksheet.iter_rows(values_only=True))
    if not raw_rows:
        return [], [
            EmployeeSpreadsheetError(
                row=0,
                column="文件",
                employee_no="",
                message="Excel 中没有可导入的数据行。",
            )
        ]

    header_row = [_normalize_cell(value) for value in raw_rows[0]]
    if list(header_row) != list(EMPLOYEE_HEADERS):
        return [], [
            EmployeeSpreadsheetError(
                row=1,
                column="表头",
                employee_no="",
                message="表头与员工导入模板不一致，请下载最新模板后重试。",
            )
        ]

    parsed_rows: list[EmployeeImportRow] = []
    for index, raw_row in enumerate(raw_rows[1:], start=2):
        if index - 1 > MAX_IMPORT_ROWS:
            errors.append(
                EmployeeSpreadsheetError(
                    row=index,
                    column="文件",
                    employee_no="",
                    message=f"单次最多导入 {MAX_IMPORT_ROWS} 行数据。",
                )
            )
            break

        if _is_empty_data_row(raw_row):
            continue

        row_errors, parsed = _parse_data_row(index, raw_row)
        errors.extend(row_errors)
        if parsed is not None:
            parsed_rows.append(parsed)

    if not parsed_rows and not errors:
        errors.append(
            EmployeeSpreadsheetError(
                row=0,
                column="文件",
                employee_no="",
                message="Excel 中没有可导入的数据行。",
            )
        )

    return parsed_rows, errors


def _parse_data_row(
    row_number: int,
    raw_row: tuple[Any, ...],
) -> tuple[list[EmployeeSpreadsheetError], EmployeeImportRow | None]:
    errors: list[EmployeeSpreadsheetError] = []
    values = {
        HEADER_TO_FIELD[header]: _normalize_cell(raw_row[index] if index < len(raw_row) else "")
        for index, header in enumerate(EMPLOYEE_HEADERS)
    }
    employee_no = values["employee_no"]

    def add_error(column: str, message: str) -> None:
        errors.append(
            EmployeeSpreadsheetError(
                row=row_number,
                column=column,
                employee_no=employee_no,
                message=message,
            )
        )

    if not employee_no:
        add_error("员工编号*", "员工编号不能为空。")

    name = values["name"]
    if not name:
        add_error("姓名*", "姓名不能为空。")

    email = values["email"].lower() if values["email"] else ""
    if not email:
        add_error("邮箱*", "邮箱不能为空。")
    elif not _is_valid_email(email):
        add_error("邮箱*", "邮箱格式不正确。")

    position = values["position"]
    if not position:
        add_error("岗位*", "岗位不能为空。")

    grade = values["grade"]
    if not grade:
        add_error("职级*", "职级不能为空。")

    employment_status = values["employment_status"]
    if not employment_status:
        add_error("在职状态*", "在职状态不能为空。")
    elif employment_status not in VALID_EMPLOYMENT_STATUSES:
        add_error("在职状态*", "在职状态必须为：在职、试用中、停用。")

    gender = values["gender"] or None
    if gender and gender not in {"男", "女"}:
        add_error("性别", "性别只能填写：男、女，或留空。")

    birth_date, birth_error = _parse_optional_date(values["birth_date"], "出生日期")
    if birth_error:
        add_error("出生日期", birth_error)

    join_date, join_error = _parse_optional_date(values["join_date"], "入职日期")
    if join_error:
        add_error("入职日期", join_error)

    role_codes = _parse_role_codes(values["role_codes"])
    if values["role_codes"] and not role_codes:
        add_error("角色编码", "角色编码不能为空片段，多个角色请用英文逗号分隔。")

    if errors:
        return errors, None

    return (
        [],
        EmployeeImportRow(
            row_number=row_number,
            employee_no=employee_no,
            name=name,
            email=email,
            gender=gender,
            birth_date=birth_date,
            phone=values["phone"] or None,
            join_date=join_date,
            location=values["location"] or None,
            position=position,
            grade=grade,
            organization_unit_code=values["organization_unit_code"] or None,
            manager_employee_no=values["manager_employee_no"] or None,
            finance_owner_name=values["finance_owner_name"] or None,
            cost_center=values["cost_center"] or None,
            bank_account_name=values["bank_account_name"] or None,
            bank_name=values["bank_name"] or None,
            bank_account_no=values["bank_account_no"] or None,
            employment_status=employment_status,
            role_codes=role_codes or list(DEFAULT_ROLE_CODES),
        ),
    )


def _parse_role_codes(value: str) -> list[str]:
    if not value:
        return []
    codes = [item.strip() for item in value.replace("，", ",").split(",")]
    return list(dict.fromkeys(code for code in codes if code))


def _parse_optional_date(value: str, label: str) -> tuple[date | None, str | None]:
    if not value:
        return None, None

    if isinstance(value, datetime):
        return value.date(), None

    if isinstance(value, date):
        return value, None

    text = str(value).strip()
    try:
        return datetime.strptime(text, "%Y-%m-%d").date(), None
    except ValueError:
        return None, f"{label}格式必须为 YYYY-MM-DD。"


def _is_valid_email(value: str) -> bool:
    _, address = parseaddr(value)
    return bool(address) and "@" in address


def _normalize_cell(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, datetime):
        return value.strftime("%Y-%m-%d")
    if isinstance(value, date):
        return value.strftime("%Y-%m-%d")
    return str(value).strip()


def _is_empty_data_row(raw_row: tuple[Any, ...]) -> bool:
    return not any(_normalize_cell(value) for value in raw_row)
