from __future__ import annotations

from collections.abc import Callable
from datetime import UTC, date, datetime

from sqlalchemy.orm import Session

from app.core.logging import get_logger
from app.core.security import hash_password
from app.models.employee import Employee
from app.models.role import Role
from app.repositories.employee import EmployeeRepository
from app.schemas.employee import (
    EmployeeImportErrorRead,
    EmployeeImportResultRead,
    EmployeeImportSummaryRead,
)
from app.services.employee_bank_info import apply_default_bank_info
from app.services.employee_seed import normalize_organization_unit_code
from app.services.employee_spreadsheet import (
    EmployeeImportRow,
    EmployeeSpreadsheetError,
    build_export_workbook_bytes,
    parse_employee_workbook,
)

logger = get_logger("app.services.employee")


class EmployeeImportCoordinator:
    def __init__(
        self,
        db: Session,
        repository: EmployeeRepository,
        *,
        sorted_roles: Callable[[list[Role]], list[Role]],
        append_change_log: Callable[[Employee, str, str, datetime | None], None],
        format_date: Callable[[date | None], str | None],
        format_datetime: Callable[[datetime | None], str | None],
        default_password: str,
    ) -> None:
        self.db = db
        self.repository = repository
        self.sorted_roles = sorted_roles
        self.append_change_log = append_change_log
        self.format_date = format_date
        self.format_datetime = format_datetime
        self.default_password = default_password

    def export_employees(self, status: str | None = None, keyword: str | None = None) -> bytes:
        employees = self.repository.list(status=status, keyword=keyword)
        rows: list[list[str]] = []

        for employee in employees:
            organization = employee.organization_unit
            role_codes = ",".join(
                role.role_code for role in self.sorted_roles(list(employee.roles))
            )
            organization_code = (
                normalize_organization_unit_code(organization.unit_code) if organization else ""
            )
            rows.append(
                [
                    employee.employee_no,
                    employee.name,
                    employee.email,
                    employee.gender or "",
                    self.format_date(employee.birth_date) or "",
                    employee.phone or "",
                    self.format_date(employee.join_date) or "",
                    employee.location or "",
                    employee.position,
                    employee.grade,
                    organization_code or "",
                    employee.manager.employee_no if employee.manager else "",
                    employee.finance_owner_name or "",
                    employee.cost_center or "",
                    employee.bank_account_name or "",
                    employee.bank_name or "",
                    employee.bank_account_no or "",
                    employee.employment_status,
                    role_codes,
                ]
            )

        return build_export_workbook_bytes(rows)

    def import_employees(
        self, content: bytes, actor: str = "系统管理员"
    ) -> EmployeeImportResultRead:
        parsed_rows, parse_errors = parse_employee_workbook(content)
        if parse_errors:
            return self._build_import_failure(parse_errors, total_rows=len(parsed_rows))

        validation_errors = self._validate_import_rows(parsed_rows)
        if validation_errors:
            return self._build_import_failure(validation_errors, total_rows=len(parsed_rows))

        try:
            summary = self._apply_import_rows(parsed_rows, actor=actor)
        except Exception:
            self.db.rollback()
            logger.exception("Employee import failed during database write")
            raise

        imported_at = self.format_datetime(datetime.now(UTC)) or ""
        message = f"导入成功：新增 {summary['created']} 人，更新 {summary['updated']} 人。"
        logger.info(
            "Imported employees created=%d updated=%d total=%d",
            summary["created"],
            summary["updated"],
            len(parsed_rows),
        )
        return EmployeeImportResultRead(
            success=True,
            message=message,
            summary=EmployeeImportSummaryRead(
                totalRows=len(parsed_rows),
                created=summary["created"],
                updated=summary["updated"],
                errorCount=0,
            ),
            errors=[],
            importedAt=imported_at,
        )

    def _validate_import_rows(
        self, rows: list[EmployeeImportRow]
    ) -> list[EmployeeSpreadsheetError]:
        errors: list[EmployeeSpreadsheetError] = []
        employee_nos_in_file: dict[str, int] = {}
        emails_in_file: dict[str, int] = {}

        roles_by_code = {role.role_code: role for role in self.repository.list_roles()}
        organizations_by_code = {
            unit.unit_code: unit for unit in self.repository.list_organization_units()
        }
        employees_by_no = {employee.employee_no: employee for employee in self.repository.list()}
        import_employee_nos = {row.employee_no for row in rows}

        for row in rows:
            if row.employee_no in employee_nos_in_file:
                errors.append(
                    EmployeeSpreadsheetError(
                        row=row.row_number,
                        column="员工编号*",
                        employee_no=row.employee_no,
                        message=f"员工编号 {row.employee_no} 在文件中重复。",
                    )
                )
            else:
                employee_nos_in_file[row.employee_no] = row.row_number

            if row.email in emails_in_file:
                errors.append(
                    EmployeeSpreadsheetError(
                        row=row.row_number,
                        column="邮箱*",
                        employee_no=row.employee_no,
                        message=f"邮箱 {row.email} 在文件中重复。",
                    )
                )
            else:
                emails_in_file[row.email] = row.row_number

            existing_by_email = self.repository.get_by_email(row.email)
            if existing_by_email is not None and existing_by_email.employee_no != row.employee_no:
                errors.append(
                    EmployeeSpreadsheetError(
                        row=row.row_number,
                        column="邮箱*",
                        employee_no=row.employee_no,
                        message=(
                            f"邮箱 {row.email} 已被员工 {existing_by_email.employee_no} 使用。"
                        ),
                    )
                )

            organization_code = normalize_organization_unit_code(row.organization_unit_code)
            if organization_code and organization_code not in organizations_by_code:
                errors.append(
                    EmployeeSpreadsheetError(
                        row=row.row_number,
                        column="部门编码",
                        employee_no=row.employee_no,
                        message=f"部门编码 {row.organization_unit_code} 不存在。",
                    )
                )

            if row.manager_employee_no:
                manager_exists = (
                    row.manager_employee_no in employees_by_no
                    or row.manager_employee_no in import_employee_nos
                )
                if not manager_exists:
                    errors.append(
                        EmployeeSpreadsheetError(
                            row=row.row_number,
                            column="直属上级工号",
                            employee_no=row.employee_no,
                            message=f"直属上级工号 {row.manager_employee_no} 不存在。",
                        )
                    )
                if row.manager_employee_no == row.employee_no:
                    errors.append(
                        EmployeeSpreadsheetError(
                            row=row.row_number,
                            column="直属上级工号",
                            employee_no=row.employee_no,
                            message="直属上级不能是员工本人。",
                        )
                    )

            invalid_role_codes = [code for code in row.role_codes if code not in roles_by_code]
            if invalid_role_codes:
                errors.append(
                    EmployeeSpreadsheetError(
                        row=row.row_number,
                        column="角色编码",
                        employee_no=row.employee_no,
                        message=f"角色不存在：{'、'.join(invalid_role_codes)}。",
                    )
                )

        return errors

    def _apply_import_rows(
        self,
        rows: list[EmployeeImportRow],
        *,
        actor: str,
    ) -> dict[str, int]:
        roles_by_code = {role.role_code: role for role in self.repository.list_roles()}
        organizations_by_code = {
            unit.unit_code: unit for unit in self.repository.list_organization_units()
        }
        employees_by_no = {employee.employee_no: employee for employee in self.repository.list()}
        created = 0
        updated = 0
        now = datetime.now(UTC)

        for row in rows:
            employee = employees_by_no.get(row.employee_no)
            is_new = employee is None

            if is_new:
                employee = Employee(
                    tenant_id=self.repository.tenant_id,
                    employee_no=row.employee_no,
                    name=row.name,
                    email=row.email,
                    password_hash=hash_password(self.default_password),
                )
                self.db.add(employee)
                employees_by_no[row.employee_no] = employee
                created += 1
            else:
                updated += 1

            employee.name = row.name
            employee.email = row.email
            employee.gender = row.gender
            employee.birth_date = row.birth_date
            employee.phone = row.phone
            employee.join_date = row.join_date
            employee.location = row.location
            employee.position = row.position
            employee.grade = row.grade
            employee.finance_owner_name = row.finance_owner_name
            employee.cost_center = row.cost_center
            employee.bank_account_name = row.bank_account_name
            employee.bank_name = row.bank_name
            employee.bank_account_no = row.bank_account_no
            employee.employment_status = row.employment_status
            employee.sync_state = "已同步"
            employee.last_sync_at = now
            apply_default_bank_info(employee)

            organization_code = normalize_organization_unit_code(row.organization_unit_code)
            if organization_code:
                employee.organization_unit = organizations_by_code[organization_code]
            else:
                employee.organization_unit = None

            employee.roles = self.sorted_roles(
                [roles_by_code[code] for code in row.role_codes if code in roles_by_code]
            )

            action = "通过 Excel 导入新建员工档案" if is_new else "通过 Excel 导入更新员工档案"
            self.append_change_log(employee, action=action, owner=actor, occurred_at=now)

        self.db.flush()

        for row in rows:
            employee = employees_by_no[row.employee_no]
            if row.manager_employee_no:
                employee.manager = employees_by_no.get(row.manager_employee_no)
            else:
                employee.manager = None

        # 导入数据和租户成员资格由 EmployeeService 在同一事务统一提交。
        self.db.flush()

        return {"created": created, "updated": updated}

    def _build_import_failure(
        self,
        errors: list[EmployeeSpreadsheetError],
        *,
        total_rows: int,
    ) -> EmployeeImportResultRead:
        error_reads = [
            EmployeeImportErrorRead(
                row=item.row,
                column=item.column,
                employeeNo=item.employee_no,
                message=item.message,
            )
            for item in errors
        ]
        return EmployeeImportResultRead(
            success=False,
            message=(
                f"导入未执行：共发现 {len(error_reads)} 处错误，请修正后重新导入。"
                "原有员工数据未变更。"
            ),
            summary=EmployeeImportSummaryRead(
                totalRows=total_rows,
                created=0,
                updated=0,
                errorCount=len(error_reads),
            ),
            errors=error_reads,
            importedAt=None,
        )
