import openpyxl
import datetime
import os
import pandas as pd
from openpyxl import load_workbook
from sqlalchemy import create_engine, Table, MetaData, exc, insert, text, update
from sqlalchemy.sql import and_
from openpyxl.utils import get_column_letter

"""
       execl表格导入，支持合并单元格，支持单表和多表的导入。
       PS : execl数据导入时，需要进行data_mappings的配置
            多表导入不支持扩展字段导入
       @:param database_config 数据库配置
                database_config = {
                    "user": "用户名",
                    "password": "密码",
                    "host": "地址",
                    "port": "端口号",
                    "database": "数据库"
                }
       @:param execl_file execl文件路径
       @:param data_mappings 数据配置，结构如下：
            data_mappings = [
                {
                    "table_name": "thread", #导入的数据库表
                    "fields": {
                        "工程项目": "title",    #key的值对应execl表头的值，value对应数据库表的字段
                        "合同编码": "subtitle"
                    },
                    #定义默认生成规则 #key对应数据库表的字段,value对应规则方法
                     "generated_fields": {
                        "subtitle": generate_code,
                        "author": generate
                    },
                    "foreign_keys": {}
                },
                {
                    "table_name": "invoice_invoice",
                    "fields": {
                        "开票时间": "invoice_time",
                        "发票号码": "invoice_number",
                        "开票金额": "invoice_price",
                        "税金点数": "tax_rate",
                    },
                    #如果是多表导入的情况，会先插入thread的数据，然后返回新生成的主键id，作为外键插入invoice_invoice表
                    "foreign_keys": {
                        "thread_id": "thread",
                    },
                    #配置这里，会到invoice_type表里根据execl里表头为 “专/普”里的值去和 “description”字段的值进行匹配，成功后返回，主键id，作为外键插入invoice_invoice表
                    "unique_foreign_keys": {
                        "invoice_type_id": { #外键名
                            "related_table": "invoice_type",#对应的表名
                            "unique_field_excel": "专/普",#execl表头名
                            "unique_field_db": "description" #数据库表对应字段名
                        }
                    }
                },
                {
                    "table_name": "finance_transact",
                    "fields": {
                        "摘要": "summary",
                        "汇入时间": "transact_time"
                    },
                    #主表主键id 主键匹配
                    "main_foreign_keys": "id",
                    #扩展表主键id 用来关联主表和扩展表的映射关系 扩展表写入必传参数
                    "extend_foreign_keys": "finance_id",
                    #扩展表映射配置
                    "extend_fields": {
                        "import_amount": { #扩展字段名
                            "related_table": "finance_extend_field",#扩展表
                            "mapping_table": "finance_extend_data",#扩展映射数据表
                            "unique_field_excel": "汇入金额" #execl表头名
                        },
                        "export_price": {
                            "related_table": "finance_extend_field",
                            "mapping_table": "finance_extend_data",
                            "unique_field_excel": "汇出金额"
                        }
                    },
                    "unique_foreign_keys": {  
                        "thread_id": {
                            "related_table": "thread",
                            "unique_field_excel": "合同编码",
                            "unique_field_db": "subtitle"
                        },
                    },
                    # 主表匹配字段(匹配规则)
                    "unique_fields": [['transact_time', 'summary']],
                    # 作为唯一标识(外键)的字段(匹配规则)
                    "unique_foreign_fields": [{
                        "thread_id": {
                            "related_table": "thread",
                            "unique_field_excel": "合同编码",
                            "unique_field_db": "subtitle"
                        }
                    }, {
                        "account_id": {
                            "related_table": "user_base_info",
                            "unique_field_excel": "客户名称",
                            "unique_field_db": "user_name"
                        }}
                    ]
                }    
            ]
       @:param date_fields 需要处理的时间字段
       @:param import_category True: 多表导入, False: 单表导入 默认单表

"""


def execl_import(database_config, execl_file, date_fields, data_mappings, import_logs="./", import_category=False):
    # 数据库连接信息
    DATABASE_URI = "mysql+mysqldb://" + database_config['user'] + ":" + database_config[
        'password'] + "@" + database_config['host'] + ":" + database_config[
                       'port'] + "/" + database_config[
                       'database'] + "?charset=utf8"  # 替换为你的数据库URI

    engine = create_engine(DATABASE_URI, echo=True)  # 设置echo=True以打印所有执行的SQL语句

    metadata = MetaData()

    # Excel文件路径
    wb = openpyxl.load_workbook(execl_file)
    sheet = wb.active

    # 复制合并单元格数据
    merged_cells_ranges = list(sheet.merged_cells.ranges)

    # 遍历所有合并单元格
    for merged_cell in merged_cells_ranges:
        # 获取合并单元格的起始行、列和结束行、列
        min_row, min_col, max_row, max_col = merged_cell.min_row, merged_cell.min_col, merged_cell.max_row, merged_cell.max_col

        # 获取合并单元格的数据
        value = sheet.cell(row=min_row, column=min_col).value

        # 将行列索引转换为单元格引用，例如"A1:B2"
        range_string = f"{get_column_letter(min_col)}{min_row}:{get_column_letter(max_col)}{max_row}"

        # 拆分合并单元格
        sheet.unmerge_cells(range_string)

        # 将数据重新填充到原始合并的单元格中
        for row in range(min_row, max_row + 1):
            for col in range(min_col, max_col + 1):
                sheet.cell(row=row, column=col).value = value

        # 提取数据并以列表形式输出
        data = []
        for row in sheet.iter_rows(values_only=True):
            formatted_row = []
            for cell in row:
                if isinstance(cell, (datetime.datetime, datetime.date)):
                    formatted_row.append(cell.strftime("%Y-%m-%d"))
                else:
                    formatted_row.append(cell)
            data.append(formatted_row)

    data = list(sheet.values)
    # 创建 DataFrame，并将所有列的类型转为字符串
    df = pd.DataFrame(data[1:], columns=data[0])

    # 数据预处理
    df = df.astype(str)
    df.replace('nan', '', inplace=True)  # 用空字符串替换NaN
    for col in date_fields:
        df[col] = df[col].replace('', '0000-00-00')  # 使用特定日期替换空字符串

    filepath = ""
    success_count = 0
    failure_count = 0
    failures = []
    foreign_keys = {}
    mapping_table = None

    # 在数据预处理后的位置
    if not import_category:
        # 数据预处理后
        for index, row in df.iterrows():
            for mapping in data_mappings:
                table_name = mapping['table_name']
                fields = mapping['fields']
                generated_fields = mapping.get('generated_fields', {})
                table = Table(table_name, metadata, autoload_with=engine)

                unique_foreign_key_present = True
                foreign_keys = {}
                for unique_fk, fk_conf in mapping.get('unique_foreign_keys', {}).items():
                    related_table = Table(fk_conf['related_table'], metadata, autoload_with=engine)
                    unique_field_excel = fk_conf['unique_field_excel']
                    unique_field_db = fk_conf['unique_field_db']
                    with engine.begin() as connection:
                        result = connection.execute(
                            text(f"SELECT id FROM {related_table.name} WHERE {unique_field_db} = :value"),
                            {"value": row[unique_field_excel]}
                        )
                        fk_id = result.scalar()
                        if fk_id is None:
                            unique_foreign_key_present = False
                            break
                        else:
                            foreign_keys[unique_fk] = fk_id

                if not unique_foreign_key_present:
                    # 如果不存在唯一外键，则跳过此记录 并将其添加到故障列表中
                    failures.append({
                        'index': index,
                        'error': f"唯一外键 {unique_fk} 未找到值 {row[unique_field_excel]}",
                        'data': dict(row)
                    })
                    continue
                record = {}
                for cn, field in fields.items():
                    if cn in row:
                        if cn in date_fields:
                            date_value = pd.to_datetime(row[cn], errors='coerce')
                            if pd.isna(date_value):  # 处理缺少的日期值
                                record[field] = None
                            else:
                                record[field] = date_value
                        else:
                            record[field] = row[cn]

                for field, generator in generated_fields.items():
                    record[field] = generator()

                # 处理 foreign_keys 和 unique_foreign_keys
                for fk_field, fk_table in mapping.get('foreign_keys', {}).items():
                    if fk_table in foreign_keys:
                        record[fk_field] = foreign_keys[fk_table]

                for fk_field, _ in mapping.get('unique_foreign_keys', {}).items():
                    if fk_field in foreign_keys:
                        record[fk_field] = foreign_keys[fk_field]
                try:
                    with engine.begin() as connection:
                        record_exists = True
                        where = {}
                        # 检查 unique_fields
                        for unique_fields in mapping.get('unique_fields', []):
                            if all(field in record for field in unique_fields):
                                query = f"SELECT id FROM {table.name} WHERE " + " AND ".join(
                                    f"{field} = :{field}" for field in unique_fields)
                                result = connection.execute(
                                    text(query),
                                    {field: record[field] for field in unique_fields}
                                )
                                result_row = result.fetchone()
                                if result_row is None:
                                    record_exists = False
                                    break
                                else:
                                    where[mapping.get('main_foreign_keys')] = result_row[0]

                        # 检查 unique_foregard_fields
                        for unique_fk_dict in mapping.get('unique_foreign_fields', []):
                            for unique_fk, fk_conf in unique_fk_dict.items():
                                related_table = Table(fk_conf['related_table'], metadata, autoload_with=engine)
                                unique_field_excel = fk_conf['unique_field_excel']
                                unique_field_db = fk_conf['unique_field_db']
                                result = connection.execute(
                                    text(f"SELECT id FROM {related_table.name} WHERE {unique_field_db} = :value"),
                                    {"value": row[unique_field_excel]}
                                )
                                result_row = result.fetchone()
                                if result_row is None:
                                    record_exists = False
                                    break
                                else:
                                    where[unique_fk] = result_row[0]

                        if where:
                            sql = text(
                                "SELECT id FROM " + str(table) + " WHERE " + " AND ".join(
                                    f"{k} = :{k}" for k in where.keys()))
                            sql = sql.bindparams(**where)
                            result = connection.execute(sql)
                            result_row = result.fetchone()
                            if result_row is not None:
                                foreign_keys[mapping.get('extend_foreign_keys')] = result_row[0]
                            else:
                                record_exists = False
                        else:
                            record_exists = False

                        if record_exists:
                            conditions = [table.c[key] == value for key, value in where.items()]
                            stmt = (
                                update(table)
                                .where(and_(*conditions))
                                .values(record)
                            )
                            connection.execute(stmt)

                        else:
                            # 不存在，执行插入操作
                            result = connection.execute(insert(table).values(record))
                            if len(table.primary_key.columns) == 1:
                                column = list(table.primary_key.columns)[0]
                                if column.autoincrement:
                                    result = connection.execute(text('SELECT LAST_INSERT_ID()'))
                                    last_id = result.scalar()
                                    if last_id is not None:
                                        foreign_keys[mapping.get('extend_foreign_keys')] = last_id

                        if mapping.get('extend_foreign_keys') in foreign_keys:
                            data_to_insert = {}
                            for extend_field, conf in mapping.get('extend_fields', {}).items():
                                related_table = Table(conf['related_table'], metadata, autoload_with=engine)
                                mapping_table = Table(conf['mapping_table'], metadata, autoload_with=engine)
                                unique_field_excel = conf['unique_field_excel']
                                result = connection.execute(
                                    text(f"SELECT field_index FROM {related_table.name} WHERE field = :value"),
                                    {"value": extend_field}
                                )
                                result_row = result.fetchone()
                                if result_row is not None:
                                    field_index = result_row[0]
                                    # 更新data_to_insert字典
                                    data_to_insert[field_index] = df.at[index, unique_field_excel]
                            # 循环后
                        if mapping_table is not None:
                            # 检查foreign_keys字典中是否存在外键
                            extend_foreign_key = mapping.get('extend_foreign_keys')
                            if extend_foreign_key not in foreign_keys or foreign_keys[extend_foreign_key] is None:
                                print(
                                    f"在Foreign_keys中找不到外键 {extend_foreign_key} 跳过行 {index}")
                                continue
                            # 检查记录是否存在
                            result = connection.execute(
                                text(f"SELECT * FROM {mapping_table.name} WHERE {extend_foreign_key} = :value"),
                                {"value": foreign_keys[extend_foreign_key]}
                            )
                            record_extend_exists = result.fetchone() is not None
                            if record_exists and record_extend_exists:
                                # 记录存在，执行更新操作
                                stmt = (
                                    update(mapping_table)
                                    .where(mapping_table.c[extend_foreign_key] == foreign_keys[extend_foreign_key])
                                    .values(data_to_insert)
                                )
                                connection.execute(stmt)
                            else:
                                data_to_insert[mapping.get('extend_foreign_keys', "")] = foreign_keys.get(
                                    mapping.get('extend_foreign_keys'))
                                connection.execute(insert(mapping_table).values(data_to_insert))

                    success_count += 1
                except exc.SQLAlchemyError as e:
                    failures.append({
                        'index': index,
                        'error': str(e),
                        'data': record
                    })
    else:
        for index, row in df.iterrows():
            for mapping in data_mappings:
                table_name = mapping['table_name']
                fields = mapping['fields']
                generated_fields = mapping.get('generated_fields', {})

                if 'foreign_keys' not in mapping and 'unique_foreign_keys' not in mapping:  # 处理没有外键的表
                    continue

                table = Table(table_name, metadata, autoload_with=engine)
                record = {}
                for cn, field in fields.items():
                    if cn in row:
                        if cn in date_fields:
                            date_value = pd.to_datetime(row[cn], errors='coerce')
                            if pd.isna(date_value):  # 处理缺少的日期值
                                record[field] = None
                            else:
                                record[field] = date_value
                        else:
                            record[field] = row[cn]

                for field, generator in generated_fields.items():
                    record[field] = generator()

                for fk_field, fk_info in mapping.get('unique_foreign_keys', {}).items():
                    related_table = fk_info["related_table"]
                    unique_field_excel = fk_info["unique_field_excel"]
                    unique_field_db = fk_info["unique_field_db"]
                    related_table = Table(related_table, metadata, autoload_with=engine)

                    with engine.begin() as connection:
                        result = connection.execute(
                            text(f"SELECT id FROM {related_table.name} WHERE {unique_field_db} = :value"),
                            {"value": row[unique_field_excel]}
                        )
                        id_ = result.scalar()
                        record[fk_field] = id_

                for fk_field, fk_table in mapping.get('foreign_keys', {}).items():
                    if fk_table in foreign_keys:
                        record[fk_field] = foreign_keys[fk_table]

                try:
                    with engine.begin() as connection:
                        result = connection.execute(insert(table).values(record))
                        if len(table.primary_key.columns) == 1:
                            column = list(table.primary_key.columns)[0]
                            if column.autoincrement:
                                result = connection.execute(text('SELECT LAST_INSERT_ID()'))
                                last_id = result.scalar()
                                foreign_keys[table_name] = last_id
                    success_count += 1
                except exc.SQLAlchemyError as e:
                    failures.append({
                        'index': index,
                        'error': str(e),
                        'data': record
                    })

    if failures:
        print("未能执行以下记录:")
        now = datetime.datetime.now()  # 获取当前日期和时间
        timestamp = now.strftime("%Y%m%d%H%M%S")  # 格式化日期和时间
        filepath = f"error_log_{timestamp}.xlsx"  # 在文件名中添加时间戳

        # 检查文件是否已存在
        # 尝试打开文件，如果文件不存在，则设置append_data为False
        # 如果文件已存在，将数据附加到现有工作簿中
        if os.path.isfile(filepath):
            book = load_workbook(filepath)
            writer = pd.ExcelWriter(filepath, engine='openpyxl')
            writer.book = book
            append_data = True
        else:
            # 如果文件不存在，创建新的Excel文件
            writer = pd.ExcelWriter(filepath, engine='openpyxl')
            append_data = False

        sheet_name = 'Sheet1'  # 我们要写入的Sheet的名称

        for failure in failures:
            failure_count += 1
            print(f"索引: {failure['index']}, 错误: {failure['error']}, 数据: {failure['data']}")
            data = failure['data']
            data['索引'] = failure['index']  # 将索引添加到数据字典中
            data['错误'] = failure['error']  # 将错误消息添加到数据字典中

            df = pd.DataFrame(data, index=[0])  # 将字典转换为DataFrame

            startrow = writer.sheets[sheet_name].max_row if append_data else 0
            df.to_excel(writer, index=False, header=not append_data, startrow=startrow, sheet_name=sheet_name)
            append_data = True  # 设置标志以便将来的数据附加到工作簿中

        writer.save()  # 在所有数据都已写入后，保存工作簿

    return {"message": "已成功执行" + str(success_count) + "条记录，失败" + str(failure_count) + "条",
            "error_path": filepath}
