from __future__ import annotations

import re
from datetime import datetime

from sqlalchemy import select, update
from sqlalchemy.orm import Session

from app.models.org import SystemConfig

RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE = "RAW_MATERIAL_LOT_PREFIX"
RAW_MATERIAL_LOT_PREFIX_DEFAULT = "YL"
SMART_OPERATION_REPORT_CONFIG_CODE = "SMART_OPERATION_REPORT_ENABLED"
MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE = "MINIAPP_OPERATION_REPORT_LAST_SYNCED_AT"
MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_NAME = "小程序报工上次同步成功时间"
MINIAPP_OPERATION_REPORT_LAST_SYNC_FORMAT = "%Y-%m-%d %H:%M:%S"


def normalize_raw_material_lot_prefix(value: str | None) -> str:
    normalized = re.sub(r"[^A-Z0-9]", "", str(value or "").upper())
    return normalized or RAW_MATERIAL_LOT_PREFIX_DEFAULT


def normalize_enabled_config_value(value: object) -> str:
    if isinstance(value, bool):
        return "开启" if value else "关闭"
    normalized = str(value or "").strip().lower()
    if normalized in {"开启", "启用", "true", "1", "yes", "on", "enabled"}:
        return "开启"
    if normalized in {"关闭", "停用", "false", "0", "no", "off", "disabled"}:
        return "关闭"
    return "开启"


def get_system_config_value(db: Session, config_code: str, default: str = "") -> str:
    value = db.scalar(
        select(SystemConfig.config_value).where(
            SystemConfig.config_code == config_code,
            SystemConfig.status == "ACTIVE",
        )
    )
    return str(value) if value is not None else default


def get_raw_material_lot_prefix(db: Session) -> str:
    value = get_system_config_value(
        db,
        RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE,
        RAW_MATERIAL_LOT_PREFIX_DEFAULT,
    )
    return normalize_raw_material_lot_prefix(value)


def get_smart_operation_report_enabled(db: Session) -> bool:
    value = get_system_config_value(db, SMART_OPERATION_REPORT_CONFIG_CODE, "开启")
    return normalize_enabled_config_value(value) == "开启"


def format_miniapp_operation_report_last_sync(value: datetime) -> str:
    return value.strftime(MINIAPP_OPERATION_REPORT_LAST_SYNC_FORMAT)


def parse_miniapp_operation_report_last_sync(value: str | None) -> datetime | None:
    normalized = str(value or "").strip()
    if not normalized:
        return None
    return datetime.strptime(normalized, MINIAPP_OPERATION_REPORT_LAST_SYNC_FORMAT)


def get_miniapp_operation_report_last_sync(db: Session) -> datetime | None:
    value = get_system_config_value(db, MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE, "")
    return parse_miniapp_operation_report_last_sync(value)


def upsert_miniapp_operation_report_last_sync(
    db: Session,
    timestamp: datetime,
    *,
    updated_by: int | None = None,
) -> SystemConfig:
    return upsert_system_config(
        db,
        config_code=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE,
        config_name=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_NAME,
        config_value=format_miniapp_operation_report_last_sync(timestamp),
        remark="工序报工小程序同步 ERP 的上次成功截止时间",
        updated_by=updated_by,
        now=timestamp,
    )


def ensure_miniapp_operation_report_last_sync_config(
    db: Session,
    *,
    updated_by: int | None = None,
) -> SystemConfig:
    row = db.scalar(select(SystemConfig).where(SystemConfig.config_code == MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE))
    if row:
        return row
    timestamp = datetime.now()
    row = SystemConfig(
        config_code=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE,
        config_name=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_NAME,
        config_value="",
        remark="工序报工小程序同步 ERP 的上次成功截止时间",
        status="ACTIVE",
        created_by=updated_by,
        updated_by=updated_by,
        created_at=timestamp,
        updated_at=timestamp,
    )
    db.add(row)
    db.flush()
    return row


def lock_miniapp_operation_report_last_sync(db: Session) -> SystemConfig | None:
    ensure_miniapp_operation_report_last_sync_config(db)
    return db.scalar(
        select(SystemConfig)
        .where(
            SystemConfig.config_code == MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE,
            SystemConfig.status == "ACTIVE",
        )
        .with_for_update()
    )


def upsert_smart_operation_report_config(
    db: Session,
    *,
    enabled: bool,
    updated_by: int | None = None,
    remark: str | None = None,
    now: datetime | None = None,
) -> SystemConfig:
    return upsert_system_config(
        db,
        config_code=SMART_OPERATION_REPORT_CONFIG_CODE,
        config_name="对接智能报工小程序",
        config_value=normalize_enabled_config_value(enabled),
        remark=remark or "开启后显示工序报工并按小程序报工数据计算生产进度；关闭后按ERP入库数据反推生产进度",
        updated_by=updated_by,
        now=now,
    )


def lock_raw_material_lot_prefix_config(db: Session) -> None:
    """Serialize lot-number generation on databases that support row locks."""
    db.scalar(
        select(SystemConfig.id)
        .where(
            SystemConfig.config_code == RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE,
            SystemConfig.status == "ACTIVE",
        )
        .with_for_update()
    )


def upsert_system_config(
    db: Session,
    *,
    config_code: str,
    config_name: str,
    config_value: str,
    remark: str | None = None,
    updated_by: int | None = None,
    now: datetime | None = None,
) -> SystemConfig:
    timestamp = now or datetime.now()
    saved_value = (
        normalize_raw_material_lot_prefix(config_value)
        if config_code == RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE
        else str(config_value or "").strip()
    )
    row = db.scalar(select(SystemConfig).where(SystemConfig.config_code == config_code))
    if not row:
        row = SystemConfig(
            config_code=config_code,
            created_by=updated_by,
            created_at=timestamp,
        )

        row.config_name = config_name
        row.config_value = saved_value
        row.remark = remark
        row.status = "ACTIVE"
        row.updated_by = updated_by
        row.updated_at = timestamp
        db.add(row)
        db.flush()
        return row

    # Use a Core update so caller-supplied business timestamps are not overwritten by
    # the global ORM before_update audit hook.
    db.execute(
        update(SystemConfig)
        .where(SystemConfig.id == row.id)
        .values(
            config_name=config_name,
            config_value=saved_value,
            remark=remark,
            status="ACTIVE",
            updated_by=updated_by,
            updated_at=timestamp,
        )
    )
    db.flush()
    db.refresh(row)
    return row
