#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
收支明细数据模型
按月分表: in_out_detail_YYYY_MM
使用SQLAlchemy定义表结构，支持动态分表和批量upsert
"""

from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, Numeric, BigInteger, UniqueConstraint, Index
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import sessionmaker
from datetime import datetime
import time
import os

import openpyxl

Base = declarative_base()

# Excel列名 → DB列名 映射 (顺序与Excel列一致)
EXCEL_COLUMN_MAPPING = [
    ('报账单号', 'report_order_no'),
    ('货号', 'goods_sn'),
    ('SKC', 'skc_name'),
    ('平台SKU', 'sku_code'),
    ('商家SKU', 'sku_sn'),
    ('属性集', 'suffix'),
    ('商品数量', 'goods_count'),
    ('金额', 'income'),
    ('币种', 'settle_currency_code'),
    ('账单类型', 'second_order_type_name'),
    ('收支类型', 'in_and_out_name'),
    ('状态', 'settlement_status_name'),
    ('业务单号', 'bz_order_no'),
    ('来源单号', 'source_no'),
    ('账单创建时间', 'business_completed_time'),
    ('台账添加时间', 'add_time'),
    ('报账时间', 'report_time'),
    ('预计结算日期', 'estimate_pay_time'),
    ('实际结算日期', 'completed_pay_time'),
    ('公司主体', 'company_name'),
    ('活动信息', 'activity_info'),
    ('退税类型', 'tax_refund_type_name'),
    ('费用类型', 'expense_type_name'),
    ('备注', 'remark'),
    ('独立站标识', 'indie_site_flag'),
    ('下单批次', 'order_batch'),
]


def _create_in_out_detail_model(table_name):
    """
    动态创建收支明细模型类（支持按月分表）

    Args:
        table_name: 表名，如 'in_out_detail_2026_02'
    Returns:
        动态创建的模型类
    """

    class_attrs = {
        '__tablename__': table_name,
        '__table_args__': (
            UniqueConstraint('report_order_no','bz_order_no', 'expense_type_name', name=f'uk_{table_name}_unique'),
            Index(f'idx_{table_name}_store', 'store_username'),
            Index(f'idx_{table_name}_add_time', 'add_time'),
            {'extend_existing': True}
        ),
        'id': Column(Integer, primary_key=True, autoincrement=True, comment='主键ID'),
        'report_order_no': Column(String(100), nullable=True, comment='报账单号'),
        'goods_sn': Column(String(100), nullable=True, comment='货号'),
        'skc_name': Column(String(100), nullable=True, comment='SKC'),
        'sku_code': Column(String(100), nullable=True, comment='平台SKU'),
        'sku_sn': Column(String(100), nullable=True, comment='商家SKU'),
        'suffix': Column(String(200), nullable=True, comment='属性集'),
        'goods_count': Column(Integer, nullable=True, comment='商品数量'),
        'income': Column(Numeric(12, 2), nullable=True, comment='金额'),
        'settle_currency_code': Column(String(10), nullable=True, comment='币种'),
        'second_order_type_name': Column(String(50), nullable=True, comment='账单类型'),
        'in_and_out_name': Column(String(50), nullable=True, comment='收支类型'),
        'settlement_status_name': Column(String(50), nullable=True, comment='状态'),
        'bz_order_no': Column(String(200), nullable=True, comment='业务单号'),
        'source_no': Column(String(200), nullable=True, comment='来源单号'),
        'business_completed_time': Column(String(30), nullable=True, comment='账单创建时间'),
        'add_time': Column(String(30), nullable=True, comment='台账添加时间'),
        'report_time': Column(String(30), nullable=True, comment='报账时间'),
        'estimate_pay_time': Column(String(30), nullable=True, comment='预计结算日期'),
        'completed_pay_time': Column(String(30), nullable=True, comment='实际结算日期'),
        'company_name': Column(String(200), nullable=True, comment='公司主体'),
        'activity_info': Column(Text, nullable=True, comment='活动信息'),
        'tax_refund_type_name': Column(String(50), nullable=True, comment='退税类型'),
        'expense_type_name': Column(String(50), nullable=True, comment='费用类型'),
        'remark': Column(Text, nullable=True, comment='备注'),
        'indie_site_flag': Column(String(50), nullable=True, comment='独立站标识'),
        'order_batch': Column(String(50), nullable=True, comment='下单批次'),
        'store_username': Column(String(50), nullable=False, comment='店铺账号'),
        'store_name': Column(String(100), nullable=True, comment='店铺别名'),
        'created_at': Column(DateTime, default=datetime.now, comment='创建时间'),
    }

    model_class = type(f'InOutDetail_{table_name}', (Base,), class_attrs)
    return model_class


# 缓存已创建的模型类
_model_cache = {}


def get_in_out_detail_model(year, month):
    """
    获取指定月份的收支明细模型类

    Args:
        year: 年份, 如 2026
        month: 月份, 如 2
    Returns:
        模型类
    """
    table_name = f'in_out_detail_{year}_{month:02d}'
    if table_name not in _model_cache:
        _model_cache[table_name] = _create_in_out_detail_model(table_name)
    return _model_cache[table_name]


class InOutDetailManager:
    """
    收支明细数据管理器
    支持按月分表，批量upsert
    """

    def __init__(self, database_url):
        self.engine = create_engine(
            database_url,
            echo=False,
            pool_size=20,
            max_overflow=10,
            pool_recycle=3600,
            pool_pre_ping=True,
        )
        self.Session = sessionmaker(bind=self.engine, autoflush=False)

    def create_table(self, year, month):
        """创建指定月份的数据表"""
        model = get_in_out_detail_model(year, month)
        model.__table__.create(self.engine, checkfirst=True)
        print(f"[OK] 表 {model.__tablename__} 创建成功")

    def read_excel_data(self, excel_path, store_username, store_name):
        """
        从Excel文件读取收支明细数据

        Args:
            excel_path: Excel文件路径
            store_username: 店铺账号
            store_name: 店铺别名
        Returns:
            list: 数据字典列表
        """
        wb = openpyxl.load_workbook(excel_path)
        ws = wb.active

        # 检查是否有数据
        if ws.max_row is None or ws.max_row < 2:
            wb.close()
            return []

        rows = []
        for row in ws.iter_rows(values_only=True):
            rows.append(row)

        headers = [str(h).strip() if h else '' for h in rows[0]]

        # 建立 Excel列名 → DB列名 的映射
        col_map = dict(EXCEL_COLUMN_MAPPING)
        header_to_db = {}
        for idx, header in enumerate(headers):
            if header in col_map:
                header_to_db[idx] = col_map[header]

        records = []
        for row in rows[1:]:
            record = {}
            for idx, db_col in header_to_db.items():
                val = row[idx] if idx < len(row) else None
                # 清理空值
                if val is not None and str(val).strip() in ('', '/', '-'):
                    val = None
                record[db_col] = val
            record['store_username'] = store_username
            record['store_name'] = store_name
            record['created_at'] = datetime.now()
            records.append(record)

        wb.close()
        return records

    def upsert_data(self, year, month, records, batch_size=1000):
        """
        批量upsert收支明细数据

        Args:
            year: 年份
            month: 月份
            records: 数据字典列表
            batch_size: 每批次大小
        """
        if not records:
            print("没有数据需要插入")
            return

        model = get_in_out_detail_model(year, month)
        self.create_table(year, month)

        max_retries = 3
        with self.engine.connect() as conn:
            for i in range(0, len(records), batch_size):
                batch = records[i:i + batch_size]
                stmt = insert(model).values(batch)
                stmt = stmt.on_duplicate_key_update(
                    report_order_no=stmt.inserted.report_order_no,
                    income=stmt.inserted.income,
                    settlement_status_name=stmt.inserted.settlement_status_name,
                    completed_pay_time=stmt.inserted.completed_pay_time,
                    report_time=stmt.inserted.report_time,
                    remark=stmt.inserted.remark,
                )
                for retry in range(max_retries):
                    try:
                        conn.execute(stmt)
                        break
                    except OperationalError as e:
                        if 'Deadlock' in str(e) and retry < max_retries - 1:
                            print(f"[WARN] 死锁，重试 {retry + 1}/{max_retries}...")
                            time.sleep(1)
                            continue
                        raise
            conn.commit()

        print(f"[OK] 成功 upsert {len(records)} 条收支明细数据到 {model.__tablename__}")

    def import_from_excel_files(self, excel_paths, store_username, store_name, year, month):
        """
        从多个Excel文件导入数据

        Args:
            excel_paths: Excel文件路径列表
            store_username: 店铺账号
            store_name: 店铺别名
            year: 年份
            month: 月份
        """
        all_records = []
        for path in excel_paths:
            records = self.read_excel_data(path, store_username, store_name)
            all_records.extend(records)
            print(f"[INFO] 读取 {path}: {len(records)} 条记录")

        if all_records:
            self.upsert_data(year, month, all_records)
        else:
            print(f"[INFO] 没有收支明细数据需要导入")
