package de.avatic.lcc.service.transformer.error;

import de.avatic.lcc.dto.error.ErrorLogDTO;
import de.avatic.lcc.dto.error.ErrorLogTraceItemDto;
import de.avatic.lcc.dto.error.FrontendErrorDTO;
import de.avatic.lcc.model.db.error.SysError;
import de.avatic.lcc.model.db.error.SysErrorTraceItem;
import de.avatic.lcc.model.db.error.SysErrorType;
import de.avatic.lcc.service.users.AuthorizationService;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class SysErrorTransformer {


    private static final String TRACE_REGEX = "at\\s+(?:async\\s+)?(?:(.+?)\\s+)?\\(([^?]+(?:\\?[^:]*)?):(\\d+):\\d+\\)";
    private static final Pattern TRACE_REGEX_PATTERN = Pattern.compile(TRACE_REGEX);
    private final AuthorizationService authorizationService;

    public SysErrorTransformer(AuthorizationService authorizationService) {
        this.authorizationService = authorizationService;
    }

    public SysError toSysErrorEntity(FrontendErrorDTO frontendErrorDTO) {
        var userId = authorizationService.getUserId();

        SysError entity = new SysError();

        entity.setCode(frontendErrorDTO.getError().getCode());
        entity.setMessage(frontendErrorDTO.getError().getMessage());
        entity.setTitle(frontendErrorDTO.getError().getTitle());
        entity.setPinia(frontendErrorDTO.getState());
        entity.setRequest(frontendErrorDTO.getRequest());
        entity.setUserId(userId);

        var traceItems = frontendErrorDTO.getError().getTrace();

        if (traceItems == null) {
            entity.setType(SysErrorType.FRONTEND);

            var traceCombined = frontendErrorDTO.getError().getTraceCombined();
            if (null != traceCombined) {
               entity.setTrace(toSysErrorTraceItem(frontendErrorDTO.getError().getTraceCombined()));
            }

        } else {
            entity.setType(SysErrorType.BACKEND);
            entity.setTrace(frontendErrorDTO.getError().getTrace().stream().map(this::toSysErrorTraceItem).toList());
        }

        return entity;

    }

    private List<SysErrorTraceItem> toSysErrorTraceItem(String traceCombined) {
        List<SysErrorTraceItem> items = new ArrayList<>();
        var lines = traceCombined.split("\n");

        for (var line : lines) {
            Matcher matcher = TRACE_REGEX_PATTERN.matcher(line);

            while (matcher.find()) {
                SysErrorTraceItem item = new SysErrorTraceItem();

                item.setFile(matcher.group(2));
                item.setLine(Integer.parseInt(matcher.group(3)));
                item.setMethod(matcher.group(1));
                item.setFullPath("at " + matcher.group(1));

                items.add(item);

            }

        }

        return items;
    }

    public SysErrorTraceItem toSysErrorTraceItem(StackTraceElement traceElement) {

            SysErrorTraceItem item = new SysErrorTraceItem();
            item.setFile(traceElement.getFileName());
            item.setLine(traceElement.getLineNumber());
            item.setMethod(traceElement.getMethodName());
            item.setFullPath("at " + traceElement.getClassName() + "." + traceElement.getMethodName());



        return item;

    }

    public ErrorLogDTO toSysErrorDto(SysError sysError) {
        var dto = new ErrorLogDTO();

        dto.setUserId(sysError.getUserId());
        dto.setCode(sysError.getCode());
        dto.setMessage(sysError.getMessage());
        dto.setTitle(sysError.getTitle());
        dto.setPinia(sysError.getPinia());
        dto.setBulkOperationId(sysError.getBulkOperationId());
        dto.setCalculationJobId(sysError.getCalculationJobId());
        dto.setType(sysError.getType().name());
        dto.setTrace(sysError.getTrace().stream().map(this::toSysErrorTraceItemDto).toList());
        dto.setCreatedAt(sysError.getCreatedAt());
        dto.setRequest(sysError.getRequest());

        return dto;
    }

    private ErrorLogTraceItemDto toSysErrorTraceItemDto(SysErrorTraceItem sysErrorTraceItem) {
        var dto = new ErrorLogTraceItemDto();

        dto.setFullPath(sysErrorTraceItem.getFullPath());
        dto.setMethod(sysErrorTraceItem.getMethod());
        dto.setLine(sysErrorTraceItem.getLine());
        dto.setFile(sysErrorTraceItem.getFile());

        return dto;
    }
}
