package com.wix.rt.inspection;

import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.MultiplePsiFilesPerDocumentFileViewProvider;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.wix.ActualFile;
import com.wix.ThreadLocalActualFile;
import com.wix.annotator.ExternalLintAnnotationInput;
import com.wix.annotator.ExternalLintAnnotationResult;
import com.wix.annotator.InspectionUtil;
import com.wix.rt.RTBundle;
import com.wix.rt.RTProjectComponent;
import com.wix.rt.build.RTFileUtil;
import com.wix.rtk.cli.Result;
import com.wix.rt.build.VerifyMessage;
import com.wix.rtk.cli.RTRunner;
import com.wix.rtk.cli.RTSettings;
import com.wix.utils.Delayer;
import com.wix.utils.FileUtils;
import com.wix.utils.PsiUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.concurrent.TimeUnit;

/**
 * @author idok
 */
public class RTExternalAnnotator extends ExternalAnnotator<ExternalLintAnnotationInput, ExternalLintAnnotationResult<Result>> {

    public static final RTExternalAnnotator INSTANCE = new RTExternalAnnotator();
    private static final Logger LOG = Logger.getInstance(RTBundle.LOG_ID);
    private static final String MESSAGE_PREFIX = "RT: ";
    private static final Key<ThreadLocalActualFile> RT_TEMP_FILE_KEY = Key.create("RT_TEMP_FILE");

//    private static final ActualFileManager actualFileManager = new ActualFileManager(RT_TEMP_FILE_KEY, false, "rt", "-rt-tmp");

//    private static final int TABS = 4;
//    private int tabSize;

//    private static int getTabSize(@NotNull Editor editor) {
//        // Get tab size
//        int tabSize = 0;
//        Project project = editor.getProject();
//        PsiFile psifile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
//        CommonCodeStyleSettings commonCodeStyleSettings = new CommonCodeStyleSettings(psifile.getLanguage());
//        CommonCodeStyleSettings.IndentOptions indentOptions = commonCodeStyleSettings.getIndentOptions();
//
//        if (indentOptions != null) {
//            tabSize = commonCodeStyleSettings.getIndentOptions().TAB_SIZE;
//        }
//        if (tabSize == 0) {
//            tabSize = editor.getSettings().getTabSize(editor.getProject());
//        }
//        return tabSize;
//    }

    @Nullable
    @Override
    public ExternalLintAnnotationInput collectInformation(@NotNull PsiFile file) {
        return collectInformation(file, null);
    }

    @Nullable
    @Override
    public ExternalLintAnnotationInput collectInformation(@NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors) {
        return collectInformation(file, editor);
    }

    @Override
    public void apply(@NotNull PsiFile file, ExternalLintAnnotationResult<Result> annotationResult, @NotNull AnnotationHolder holder) {
        if (annotationResult == null) {
            return;
        }
        InspectionProjectProfileManager inspectionProjectProfileManager = InspectionProjectProfileManager.getInstance(file.getProject());
        SeverityRegistrar severityRegistrar = inspectionProjectProfileManager.getSeverityRegistrar();
        HighlightDisplayKey inspectionKey = RTInspection.getHighlightDisplayKey();
        HighlightSeverity severity = InspectionUtil.getSeverity(inspectionProjectProfileManager, inspectionKey, file);
//        RTProjectComponent component = annotationResult.input.project.getComponent(RTProjectComponent.class);
//        HighlightSeverity severity = getHighlightSeverity(warn, component.treatAsWarnings);
        EditorColorsScheme colorsScheme = annotationResult.input.colorsScheme;

        Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
        if (document == null) {
            return;
        }
        if (annotationResult.result == null || annotationResult.result.getWarns() == null) {
            LOG.warn("annotationResult.result == null");
            return;
        }
        for (VerifyMessage warn : annotationResult.result.getWarns()) {
            TextAttributes forcedTextAttributes = InspectionUtil.getTextAttributes(colorsScheme, severityRegistrar, severity);
            /*Annotation annotation = */
            severity = getHighlightSeverity(warn, false);
            createAnnotation(holder, file, document, warn, severity, forcedTextAttributes, true);
//            if (annotation != null) {
//                int offset = StringUtil.lineColToOffset(document.getText(), warn.line - 1, warn.column);
//                PsiElement lit = PsiUtil.getElementAtOffset(file, offset);
//                BaseActionFix actionFix = Fixes.getFixForRule(warn.rule, lit);
//                if (actionFix != null) {
//                    annotation.registerFix(actionFix, null, inspectionKey);
//                }
//                annotation.registerFix(new SuppressActionFix(warn.rule, lit), null, inspectionKey);
//            }
        }
    }

    private static HighlightSeverity getHighlightSeverity(VerifyMessage warn, boolean treatAsWarnings) {
        if (treatAsWarnings) {
            return HighlightSeverity.WARNING;
        }
        return warn.level.equals("ERROR") ? HighlightSeverity.ERROR : HighlightSeverity.WARNING;
    }

    @Nullable
    private static Annotation createAnnotation(@NotNull AnnotationHolder holder, @NotNull PsiFile file, @NotNull Document document, @NotNull VerifyMessage warn,
                                               @NotNull HighlightSeverity severity, @Nullable TextAttributes forcedTextAttributes,
                                               boolean showErrorOnWholeLine) {
        if (warn.startOffset > -1 && warn.endOffset > -1) {
            return createAnnotationByOffset(holder, warn, severity, forcedTextAttributes);
        }
        if (warn.index > -1) {
            return createAnnotationByIndex(holder, file, document, warn, severity, forcedTextAttributes, showErrorOnWholeLine);
        }

        int line = Math.max(warn.line - 1, 0);
        int column = Math.max(warn.column, 0);

        if (line < 0 || line >= document.getLineCount()) {
            return null;
        }
        int lineEndOffset = document.getLineEndOffset(line);
        int lineStartOffset = document.getLineStartOffset(line);

        int errorLineStartOffset = StringUtil.lineColToOffset(document.getCharsSequence(), line, column);
//        int errorLineStartOffset = PsiUtil.calcErrorStartOffsetInDocument(document, lineStartOffset, lineEndOffset, column, tab);

        if (errorLineStartOffset == -1) {
            return null;
        }
//        PsiElement element = file.findElementAt(errorLineStartOffset);
        TextRange range;
        if (showErrorOnWholeLine) {
            range = new TextRange(lineStartOffset, lineEndOffset);
        } else {
//            int offset = StringUtil.lineColToOffset(document.getText(), warn.line - 1, warn.column);
            PsiElement lit = PsiUtil.getElementAtOffset(file, errorLineStartOffset);
            range = lit.getTextRange();
//            range = new TextRange(errorLineStartOffset, errorLineStartOffset + 1);
        }

        Annotation annotation = InspectionUtil.createAnnotation(holder, severity, forcedTextAttributes, range, MESSAGE_PREFIX + warn.msg.trim());
        if (annotation != null) {
            annotation.setAfterEndOfLine(errorLineStartOffset == lineEndOffset);
        }
        return annotation;
    }

    @Nullable
    private static Annotation createAnnotationByIndex(@NotNull AnnotationHolder holder, @NotNull PsiFile file, @NotNull Document document, @NotNull VerifyMessage warn,
                                                      @NotNull HighlightSeverity severity, @Nullable TextAttributes forcedTextAttributes,
                                                      boolean showErrorOnWholeLine) {
        int line = StringUtil.offsetToLineNumber(document.getCharsSequence(), warn.index);
        line = Math.max(line, 0);
        int lineEndOffset = document.getLineEndOffset(line);
        int start = document.getLineStartOffset(line);
        TextRange range;
        if (showErrorOnWholeLine) {
            range = new TextRange(start, lineEndOffset);
        } else {
//            int offset = StringUtil.lineColToOffset(document.getText(), warn.line - 1, warn.column);
            PsiElement lit = PsiUtil.getElementAtOffset(file, line);
            range = lit.getTextRange();
//            range = new TextRange(errorLineStartOffset, errorLineStartOffset + 1);
        }

        Annotation annotation = InspectionUtil.createAnnotation(holder, severity, forcedTextAttributes, range, MESSAGE_PREFIX + warn.msg.trim());
        if (annotation != null) {
            annotation.setAfterEndOfLine(line == lineEndOffset);
        }
        return annotation;
    }

    @Nullable
    private static Annotation createAnnotationByOffset(@NotNull AnnotationHolder holder, @NotNull VerifyMessage warn,
                                                       @NotNull HighlightSeverity severity, @Nullable TextAttributes forcedTextAttributes) {
        TextRange range = new TextRange(warn.startOffset, warn.endOffset);
        return InspectionUtil.createAnnotation(holder, severity, forcedTextAttributes, range, MESSAGE_PREFIX + warn.msg.trim());
    }

    @Nullable
    private static ExternalLintAnnotationInput collectInformation(@NotNull PsiFile psiFile, @Nullable Editor editor) {
        if (psiFile.getContext() != null || !RTFileUtil.isRTFile(psiFile)) {
            return null;
        }
        VirtualFile virtualFile = psiFile.getVirtualFile();
        if (virtualFile == null || !virtualFile.isInLocalFileSystem()) {
            return null;
        }
        if (psiFile.getViewProvider() instanceof MultiplePsiFilesPerDocumentFileViewProvider) {
            return null;
        }
        Project project = psiFile.getProject();
        RTProjectComponent component = project.getComponent(RTProjectComponent.class);
        if (component == null || !component.isValidAndEnabled()) {
            return null;
        }
        Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile);
        if (document == null) {
            return null;
        }
        String fileContent = document.getText();
        if (StringUtil.isEmptyOrSpaces(fileContent)) {
            return null;
        }
        EditorColorsScheme colorsScheme = editor == null ? null : editor.getColorsScheme();
//        tabSize = getTabSize(editor);
//        tabSize = 4;
        return new ExternalLintAnnotationInput(project, psiFile, fileContent, colorsScheme);
    }

    @Nullable
    @Override
    public ExternalLintAnnotationResult<Result> doAnnotate(ExternalLintAnnotationInput collectedInfo) {
        ActualFile actualCodeFile = null;
        try {
            PsiFile file = collectedInfo.psiFile;
            if (!RTFileUtil.isRTFile(file)) return null;
            Project project = file.getProject();
            RTProjectComponent component = project.getComponent(RTProjectComponent.class);
            if (component == null || !component.isValidAndEnabled()) {
                return null;
            }

            String relativeFile;
            actualCodeFile = ActualFile.getOrCreateActualFile(RT_TEMP_FILE_KEY, file.getVirtualFile(), collectedInfo.fileContent);
            if (actualCodeFile == null || actualCodeFile.getActualFile() == null) {
                return null;
            }
            relativeFile = FileUtils.makeRelative(new File(project.getBasePath()), actualCodeFile.getActualFile());
            RTSettings settings = RTSettings.buildSettings(component.settings, project.getBasePath(), relativeFile);
            Result result = RTRunner.INSTANCE.compile(settings);

            if (StringUtils.isNotEmpty(result.getErrorOutput())) {
                component.showInfoNotification(result.getErrorOutput(), NotificationType.WARNING);
                return null;
            }
            Document document = PsiDocumentManager.getInstance(project).getDocument(file);
            if (document == null) {
                component.showInfoNotification("Error running RT inspection: Could not get document for file " + file.getName(), NotificationType.WARNING);
                LOG.error("Could not get document for file " + file.getName());
                return null;
            }
            return new ExternalLintAnnotationResult<Result>(collectedInfo, result);
        } catch (Exception e) {
            LOG.error("Error running RT inspection: ", e);
            showNotification("Error running RT inspection: " + e.getMessage(), NotificationType.ERROR);
//            return new ExternalLintAnnotationResult<Result>(collectedInfo, result); file level annotation
        } finally {
            if (actualCodeFile != null) {
                actualCodeFile.deleteTemp();
            }
        }
        return null;
    }

    private final Delayer delayer = new Delayer(TimeUnit.SECONDS.toMillis(5L));

    public void showNotification(String content, NotificationType type) {
        if (delayer.should()) {
            RTProjectComponent.showNotification(content, type);
            delayer.done();
        }
    }
}