package ru.yandex.autodoc.common.doc.view.handlers;

import org.apache.commons.lang3.StringUtils;
import ru.yandex.autodoc.common.doc.ExtraInfoItem;
import ru.yandex.autodoc.common.doc.abstracts.MethodDocumentation;
import ru.yandex.autodoc.common.doc.params.ParamDescriptor;
import ru.yandex.autodoc.common.doc.result.MethodResult;
import ru.yandex.autodoc.common.doc.view.Markup;
import ru.yandex.autodoc.common.out.json.JsonValueWriter;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author avhaliullin
 */
public class MethodDocumentationMarkuper {
    private final ErrorsDocumentationMarkuper errorsDocumentationMarkuper;
    private final ObjectFormatResolver objectFormatResolver;

    public MethodDocumentationMarkuper(ErrorsDocumentationMarkuper errorsDocumentationMarkuper,
                                       ObjectFormatResolver objectFormatResolver) {
        this.errorsDocumentationMarkuper = errorsDocumentationMarkuper;
        this.objectFormatResolver = objectFormatResolver;
    }

    public Markup toMarkup(MethodDocumentation method) {
        List<Markup> blocks = new ArrayList<>();
        blocks.add(methodDesc(method));
        blocks.add(methodParams(method));
        blocks.add(methodOutput(method));
        blocks.add(methodErrors(method));
        blocks.addAll(customBlocks(method));
        return new Markup.Group(blocks);
    }

    private Markup methodDesc(MethodDocumentation method) {
        return new Markup.Section(
                "desc",
                "Описание",
                (method.needAuthorization()
                        ? Markup.b("Требуется авторизация")
                        : Markup.EMPTY).concat(
                        new Markup.Block(Markup.textOrEmpty(method.getDocumentation())
                        )
                )
        );
    }

    public Markup paramDescription(ParamDescriptor param) {
        Markup result = new Markup.Text(param.getDescription() == null ? "-" : param.getDescription());
        if (!param.getExtraInfos().isEmpty()) {
            List<Markup> extraItems = param.getExtraInfos().stream().map(info -> {
                Markup extraContent;
                if (info.getContent() instanceof ExtraInfoItem.TextExtraInfo) {
                    extraContent = Markup.textOrEmpty(((ExtraInfoItem.TextExtraInfo) info.getContent()).info);
                } else if (info.getContent() instanceof ExtraInfoItem.JsonExtraInfo) {
                    extraContent = new Markup.DocumentedJson(((ExtraInfoItem.JsonExtraInfo) info.getContent()).info);
                } else {
                    extraContent = Markup.textOrEmpty(info.getContent().toString());
                }
                if (info.getTitle() == null) {
                    return extraContent;
                } else {
                    return new Markup.Spoiler(info.getTitle(), extraContent);
                }
            }).collect(Collectors.toList());
            result.concat(new Markup.UL(extraItems));
        }
        return result;
    }

    public Markup methodParams(MethodDocumentation method) {
        List<ParamDescriptor> paramList = method.getParamDescriptors();
        String paramsDesc = method.getParamsStringDescription();
        Markup content;
        if (paramList.isEmpty() && StringUtils.isEmpty(paramsDesc)) {
            content = Markup.textOrEmpty("нет");
        } else {
            content = Markup.textOrEmpty(paramsDesc);
            if (!paramList.isEmpty()) {
                content = content.concat(paramDescriptionTable(paramList));
            }
        }
        return new Markup.Section("params", "Параметры", content);
    }

    public Markup paramDescriptionTable(List<ParamDescriptor> params) {
        return Markup.table(params,
                "Имя параметра", "Обязательный", "Тип", "Значение по умолчанию", "Описание",
                p -> Markup.textOrEmpty(p.getName()),
                p -> Markup.textOrEmpty(p.isRequired() ? "Да" : "Нет"),
                p -> TypeMarkupUtil.typeToMarkup(objectFormatResolver, p.getType(), p.getName()),
                p -> p.isRequired() ? Markup.EMPTY : Markup.textOrEmpty(p.getDefaultValue()),
                this::paramDescription
        );
    }

    public Markup methodErrors(MethodDocumentation method) {
        if (method.getMethodErrors() != null) {
            return new Markup.Section(
                    "errors",
                    "Ошибки",
                    errorsDocumentationMarkuper.errorListToMarkup(method.getMethodErrors())
            );
        } else {
            return Markup.EMPTY;
        }
    }

    private String outputFormatName(MethodResult.Type format) {
        switch (format) {
            case JSON:
                return "JSON";
            case CUSTOM:
                return "Кастомный";
            case NONE:
                return "Выдача отсутсвует";
            case OBJECT:
                return "Объект";
            default:
                return "неизвестно";
        }
    }

    public Markup methodOutput(MethodDocumentation method) {
        Markup content;
        if (method.getMethodResult() instanceof JsonValueWriter) {
            content = new Markup.DocumentedJson((JsonValueWriter) method.getMethodResult());
        } else if (method.getMethodResult() instanceof MethodResult.ObjectResult) {
            content = objectFormatResolver.resolve(((MethodResult.ObjectResult) method.getMethodResult()).getModel());
        } else {
            content = new Markup.Text("нет");
        }
        return new Markup.Section(
                "output",
                "Формат выдачи - " + outputFormatName(method.getMethodResult().getType()),
                content
        );
    }

    private List<Markup> customBlocks(MethodDocumentation method) {
        return method.extraInfoBlocks().entrySet().stream()
                .map(block -> new Markup.Section(block.getKey(), block.getKey(), block.getValue()))
                .collect(Collectors.toList());
    }
}
