package com.caliverse.admin.global.component;

import com.caliverse.admin.domain.entity.log.LogAction;
import com.caliverse.admin.domain.entity.log.LogStatus;
import com.caliverse.admin.global.common.constants.MysqlConstants;
import com.caliverse.admin.global.component.manager.BeforeDataResolver;
import com.caliverse.admin.global.component.manager.BusinessProcessIdManager;
import com.caliverse.admin.mongodb.service.MysqlHistoryLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.executor.Executor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Component
@Slf4j
public class MysqlLoggingInterceptor implements Interceptor {
    private final BusinessProcessIdManager processIdManager;
    private final MysqlHistoryLogService mysqlHistoryLogService;
    private final BeforeDataResolver beforeDataResolver;

    public MysqlLoggingInterceptor(MysqlHistoryLogService mysqlHistoryLogService, BusinessProcessIdManager processIdManager, BeforeDataResolver beforeDataResolver) {
        this.mysqlHistoryLogService = mysqlHistoryLogService;
        this.beforeDataResolver = beforeDataResolver;
        this.processIdManager = processIdManager;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];

        String sqlId = mappedStatement.getId();
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        if (!shouldLog(sqlId, sqlCommandType)) {
            return invocation.proceed();
        }
        if(sqlId.contains("Message")){
            return invocation.proceed();
        }

        // Before 데이터 조회
        Object beforeData = null;
        if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.DELETE) {
            beforeData = beforeDataResolver.resolveBeforeData(sqlId, parameter);
        }

        Object result = invocation.proceed();

        // After 데이터 조회
        Object afterData = null;
        if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.INSERT) {
            afterData = beforeDataResolver.resolveBeforeData(sqlId, parameter);
        }

        scheduleLoggingAfterTransaction(sqlId, sqlCommandType, beforeData, afterData);

        return result;
    }

    private boolean shouldLog(String sqlId, SqlCommandType sqlCommandType) {
        return (sqlCommandType == SqlCommandType.INSERT ||
                        sqlCommandType == SqlCommandType.UPDATE ||
                        sqlCommandType == SqlCommandType.DELETE);
    }

    private void scheduleLoggingAfterTransaction(String sqlId, SqlCommandType sqlCommandType,
                                                 Object beforeData, Object result) {
        String processId = processIdManager.getCurrentProcessId();
        LogAction action = processIdManager.getCurrentAction();

        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            logDatabaseOperation(sqlId, sqlCommandType, beforeData, result, processId, action);
            return;
        }

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                try {
                    logDatabaseOperation(sqlId, sqlCommandType, beforeData, result, processId, action);
                } catch (Exception e) {
                    log.warn("Failed to log after transaction commit: {}", e.getMessage());
                }
            }
        });
    }

    private void logDatabaseOperation(String sqlId, SqlCommandType sqlCommandType,
                                      Object beforeData, Object result, String processId, LogAction action) {
        String tableName = extractTableName(sqlId);

        if(beforeData == null && result == null) return;

        // 로깅 수행
        switch (sqlCommandType) {
            case INSERT -> mysqlHistoryLogService.insertHistoryLog(
                    processId, action, LogStatus.SUCCESS, tableName, "", result
            );
            case UPDATE -> mysqlHistoryLogService.updateHistoryLog(
                    processId, action, LogStatus.SUCCESS, tableName, "", beforeData, result
            );
            case DELETE -> mysqlHistoryLogService.deleteHistoryLog(
                    processId, action, LogStatus.SUCCESS, tableName, "", beforeData
            );
        }
    }

    private String extractTableName(String sqlId) {
        // sqlId에서 테이블명 추출 로직
        if (sqlId.contains("Admin")) return MysqlConstants.TABLE_NAME_ADMIN;
        if (sqlId.contains("Banner") && sqlId.contains("Menu")) return MysqlConstants.TABLE_NAME_MENU_BANNER;
        if (sqlId.contains("BlackList")) return MysqlConstants.TABLE_NAME_USER_BLOCK;
        if (sqlId.contains("Calium")) return MysqlConstants.TABLE_NAME_CALIUM_REQUEST;
        if (sqlId.contains("Data")) return MysqlConstants.TABLE_NAME_DATA_INIT;
        if (sqlId.contains("Battle")) return MysqlConstants.TABLE_NAME_BATTLE_EVENT;
        if (sqlId.contains("Event")) return MysqlConstants.TABLE_NAME_EVENT;
        if (sqlId.contains("GroupAuth")) return MysqlConstants.TABLE_NAME_GROUP_AUTH;
        if (sqlId.contains("Group")) return MysqlConstants.TABLE_NAME_GROUP;
        if (sqlId.contains("Mail")) return MysqlConstants.TABLE_NAME_MAIL;
        if (sqlId.contains("Notice")) return MysqlConstants.TABLE_NAME_NOTICE;
        if (sqlId.contains("LandAuction")) return MysqlConstants.TABLE_NAME_LAND_AUCTION;
        if (sqlId.contains("LandOwner")) return MysqlConstants.TABLE_NAME_LAND_OWNER_CHANGE;

        return "unknown";
    }

}
