package com.caliverse.admin.dynamodb.service;

import com.caliverse.admin.global.component.transaction.DynamoDBTransactionContext;
import com.caliverse.admin.global.common.code.CommonCode;
import com.caliverse.admin.global.common.exception.RestApiException;
import com.caliverse.admin.global.common.code.ErrorCode;
import com.caliverse.admin.global.common.utils.CommonUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.enhanced.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;

@Component
@Slf4j
@RequiredArgsConstructor
public class DynamoDBOperations {
    private final DynamoDbEnhancedClient enhancedClient;
    @Value("${amazon.dynamodb.metaTable}")
    private String metaTable;
    private final ObjectMapper objectMapper;
    private final ObjectMapper mapper;

    /**
     * 항목 추가
     */
    public <T> void addPutItem(T item, Class<T> itemClass) {
        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(itemClass);
            DynamoDBTransactionContext.getTransactionBuilder()
                    .addPutItem(table, item);
        });
    }

    /**
     * 조건부 항목 추가
     */
    public <T> void addConditionPutItem(T item, Class<T> itemClass, Expression condition) {
        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(itemClass);

            DynamoDBTransactionContext.getTransactionBuilder()
                    .addPutItem(table, TransactPutItemEnhancedRequest.builder(itemClass)
                            .item(item)
                            .conditionExpression(condition)
                            .build());
        });
    }

    /**
     * 항목 업데이트
     */
    public <T> void addUpdateItem(T item, Class<T> itemClass) {
        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(itemClass);
            DynamoDBTransactionContext.getTransactionBuilder()
                    .addUpdateItem(table, item);
        });
    }

    /**
     * 조건부 항목 업데이트
     */
    public <T> void addConditionUpdateItem(T item, Class<T> itemClass, Expression condition) {
        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(itemClass);

            DynamoDBTransactionContext.getTransactionBuilder()
                    .addUpdateItem(table, TransactUpdateItemEnhancedRequest.builder(itemClass)
                            .item(item)
                            .conditionExpression(condition)
                            .build());
        });
    }

    /**
     * 항목 삭제
     */
    public <T> void addDeleteItem(Key key, Class<T> itemClass) {
        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(itemClass);
            DynamoDBTransactionContext.getTransactionBuilder()
                    .addDeleteItem(table, key);
        });
    }

    /**
     * 조건부 항목 삭제
     */
    public <T> void addConditionDeleteItem(Key key, Class<T> itemClass, Expression condition) {
        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(itemClass);

            DynamoDBTransactionContext.getTransactionBuilder()
                    .addDeleteItem(table, TransactDeleteItemEnhancedRequest.builder()
                            .key(key)
                            .conditionExpression(condition)
                            .build());
        });
    }

    /**
     * 단일 항목 조회 (트랜잭션 외부)
     */
    public <T> T getItem(Key key, Class<T> itemClass) {
        DynamoDbTable<T> table = getTable(itemClass);
        return table.getItem(key);
    }

    public <T> List<T> getQueryItems(Key key, Class<T> itemClass){
        DynamoDbTable<T> table = getTable(itemClass);
        QueryConditional queryConditional = QueryConditional.keyEqualTo(key);

        return table.query(r -> r.queryConditional(queryConditional))
                .items()
                .stream()
                .toList();
    }

    public <T> List<T> getItemsScanBegins(String prefix, Class<T> itemClass){
        DynamoDbTable<T> table = getTable(itemClass);

        TableSchema<T> schema = table.tableSchema();
        String partitionKeyName = schema.tableMetadata().primaryPartitionKey();

        Expression filterExpression = Expression.builder()
                .expression("begins_with(#pk, :prefix)")
                .putExpressionName("#pk", partitionKeyName)
                .putExpressionValue(":prefix", AttributeValue.builder().s(prefix).build())
                .build();

        return table.scan(r -> r.filterExpression(filterExpression))
                .items()
                .stream()
                .toList();
    }

    public <T> List<T> getItemsScanBegins(String prefix, String sortKeyPrefix, Class<T> entityClass) {
        DynamoDbTable<T> table = getTable(entityClass);

        // 파티션 키 및 정렬 키 속성 이름 가져오기
        TableSchema<T> schema = table.tableSchema();
        String partitionKeyName = schema.tableMetadata().primaryPartitionKey();
        String sortKeyName = String.valueOf(schema.tableMetadata().primarySortKey().get());

        // 표현식 빌더 초기화
        Expression.Builder expressionBuilder = Expression.builder();
        StringBuilder expressionString = new StringBuilder();

        // 파티션 키 조건 추가
        expressionString.append("begins_with(#pk, :prefix)");
        expressionBuilder.putExpressionName("#pk", partitionKeyName);
        expressionBuilder.putExpressionValue(":prefix", AttributeValue.builder().s(prefix).build());

        // 정렬 키 조건 추가 (테이블에 정렬 키가 있는 경우)
        if (sortKeyName != null && sortKeyPrefix != null && !sortKeyPrefix.isEmpty()) {
            expressionString.append(" AND begins_with(#sk, :sortPrefix)");
            expressionBuilder.putExpressionName("#sk", sortKeyName);
            expressionBuilder.putExpressionValue(":sortPrefix", AttributeValue.builder().s(sortKeyPrefix).build());
        }

        // 필터 표현식 완성
        Expression filterExpression = expressionBuilder
                .expression(expressionString.toString())
                .build();

        return table.scan(r -> r.filterExpression(filterExpression))
                .items()
                .stream()
                .toList();
    }

    /**
     * JSON 문자열을 객체로 변환하고 트랜잭션에 추가
     */
    public <T> void addJsonItem(String json, Class<T> itemClass) {
        try {
            T item = objectMapper.readValue(json, itemClass);
            addPutItem(item, itemClass);
        } catch (JsonProcessingException e) {
            log.error("JSON parsing failed: {}", e.getMessage());
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(),
                    ErrorCode.DYNAMODB_JSON_PARSE_ERROR.getMessage());
        }
    }

    /**
     * 객체를 이용한 attribute 업데이트
     */
    public <T, A> void addUpdateAttribute(Key key, Class<T> docClass, A attributeValue,
                                          Class<A> attributeClass) {
        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(docClass);

            // 기존 아이템 조회
            T existingDoc = table.getItem(key);
            if (existingDoc == null) {
                throw new RestApiException(CommonCode.ERROR.getHttpStatus(),
                        ErrorCode.DYNAMODB_CONNECTION_ERROR.getMessage());
            }

            try {
                // Reflection으로 attribValue getter/setter 접근
                Method getAttribValue = docClass.getMethod("getAttribValue");
                Method setAttribValue = docClass.getMethod("setAttribValue", String.class);
                Method setUpdatedDateTime = docClass.getMethod("setUpdatedDateTime", String.class);

                // 새로운 attribute 값을 JSON으로 변환
                String afterJson = mapper.writeValueAsString(attributeValue);

                // 문서 업데이트
                setAttribValue.invoke(existingDoc, afterJson);
                setUpdatedDateTime.invoke(existingDoc,
                        CommonUtils.convertUTCDate(LocalDateTime.now()));

                // 트랜잭션에 업데이트 작업 추가
                DynamoDBTransactionContext.getTransactionBuilder()
                        .addUpdateItem(table, existingDoc);

            } catch (Exception e) {
                log.error("Failed to update attribute: {}", e.getMessage());
                throw new RestApiException(CommonCode.ERROR.getHttpStatus(),
                        ErrorCode.DYNAMODB_UPDATE_ERROR.getMessage());
            }
        });
    }

    /**
     * 배치 작업 처리
     */
    public <T> void addBatchWriteItems(List<T> items, Class<T> itemClass) {
        if (items.isEmpty()) {
            return;
        }

        executeInTransaction(() -> {
            DynamoDbTable<T> table = getTable(itemClass);
            for (T item : items) {
                DynamoDBTransactionContext.getTransactionBuilder()
                        .addPutItem(table, item);
            }
        });
    }

    // Helper methods
    private <T> DynamoDbTable<T> getTable(Class<T> itemClass) {
        return enhancedClient.table(metaTable, TableSchema.fromBean(itemClass));
    }

    private void executeInTransaction(Runnable operation) {
        if (!DynamoDBTransactionContext.isInTransaction()) {
            throw new IllegalStateException("Not in a DynamoDB transaction context");
        }
        operation.run();
    }

    public <T> List<T> getItemsByPrefix(String partitionKey, String sortKeyPrefix, Class<T> itemClass) {
        DynamoDbTable<T> table = getTable(itemClass);

        QueryConditional queryConditional = QueryConditional.keyEqualTo(b -> b
                .partitionValue(partitionKey)
                .sortValue(sortKeyPrefix)
                .build());

        return table.query(r -> r
                        .queryConditional(QueryConditional.sortBeginsWith(b -> b
                                .partitionValue(partitionKey)
                                .sortValue(sortKeyPrefix)))
                )
                .items()
                .stream()
                .toList();
    }

    public <T> List<T> getItemsByPrefix(String partitionKey, Class<T> itemClass) {
        DynamoDbTable<T> table = getTable(itemClass);

        QueryConditional queryConditional = QueryConditional.keyEqualTo(b -> b
                .partitionValue(partitionKey)
                .build());

        return table.query(r -> r.queryConditional(queryConditional))
                .items()
                .stream()
                .toList();
    }

}
