package ru.yandex.qe.dispenser.ws.aspect;

import java.util.Optional;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.jetbrains.annotations.NotNull;

import ru.yandex.qe.dispenser.domain.Person;
import ru.yandex.qe.dispenser.domain.PersonAffiliation;
import ru.yandex.qe.dispenser.domain.Project;
import ru.yandex.qe.dispenser.domain.Service;
import ru.yandex.qe.dispenser.domain.aspect.AspectBase;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.hierarchy.Role;
import ru.yandex.qe.dispenser.domain.hierarchy.Session;
import ru.yandex.qe.dispenser.ws.Access;
import ru.yandex.qe.dispenser.ws.reqbody.QuotaChangeBody;

@Aspect
public final class AccessAspect extends AspectBase {
    @Before("execution(public * *(..)) && @annotation(access)")
    public void around(@NotNull final JoinPoint jp, @NotNull final Access access) {
        final Person whoami = Optional.ofNullable(Session.AUTH_LOGIN.get())
                .map(Hierarchy.get().getPersonReader()::readPersonByLogin)
                .orElseThrow(() -> new UnauthorizedException("No personal authorizarion in request! Please, specify OAuth token or passport cookies or TVM tickets."));
        checkAffiliation(whoami);
        Session.WHOAMI.set(whoami);

        if (access.dispenserAdmin()) {
            checkDispenserAdmin(whoami);
        }

        for (final Role role : access.role()) {
            Project project = getProjectFromParams(jp);
            if (access.parent()) {
                if (project.isRoot()) {
                    checkServiceAdmin(whoami, getServiceFromParams(jp));
                    return;
                }
                project = project.getParent();
            }
            checkRole(whoami, project, role);
        }

        if (access.serviceAdmin()) {
            final Service service = getServiceFromParams(jp);
            checkServiceAdmin(whoami, service);
        }

        if (access.serviceTrustee()) {
            final QuotaChangeBody body = getFirstInstance(jp, QuotaChangeBody.class); // TODO: fix QuotaChangeBody.class hardcode
            if (body != null) {
                final Service service = body.getService();
                checkServiceTrustee(whoami, service);
            }
        }
    }

    private void checkAffiliation(Person person) {
        if (person.getAffiliation() != PersonAffiliation.YANDEX && !person.isRobot()) {
            throw new ForbiddenException("Only internal Yandex users may access Dispenser");
        }
    }

    @NotNull
    private Project getProjectFromParams(@NotNull final JoinPoint jp) {
        return (Project) getPathParam(jp, Access.PROJECT_KEY);
    }

    @NotNull
    private Service getServiceFromParams(@NotNull final JoinPoint jp) {
        return (Service) getPathParam(jp, Access.SERVICE_KEY);
    }

    public static boolean isDispenserAdmin(@NotNull final Person person) {
        return Hierarchy.get().getDispenserAdminsReader().getDispenserAdmins().contains(person);
    }

    public static boolean isServiceAdmin(@NotNull final Person person, @NotNull final Service service) {
        return Hierarchy.get().getServiceReader().getAdmins(service).contains(person) || isDispenserAdmin(person);
    }

    public static boolean isRealResponsible(@NotNull final Person person, @NotNull final Project project) {
        return Hierarchy.get().getProjectReader().hasRole(person, project, Role.RESPONSIBLE);
    }

    public static boolean isResponsible(@NotNull final Person person, @NotNull final Project project) {
        return isRealResponsible(person, project) || isDispenserAdmin(person);
    }

    public static boolean isMember(@NotNull final Person person, @NotNull final Project project) {
        return Hierarchy.get().getProjectReader().hasRole(person, project, Role.MEMBER) || isRealResponsible(person, project);
    }

    public static void checkDispenserAdmin(@NotNull final Person person) {
        if (!isDispenserAdmin(person)) {
            throw new ForbiddenException("'" + person.getLogin() + "' isn't Dispenser admin!");
        }
    }

    public static void checkRole(@NotNull final Person person, @NotNull final Project project, @NotNull final Role role) {
        final boolean hasAccess = role == Role.RESPONSIBLE ? isResponsible(person, project) : isMember(person, project);
        if (!hasAccess) {
            throw new ForbiddenException("'" + person.getLogin() + "' has no access to edit project '" + project.getName() + "'!");
        }
    }

    public static void checkServiceAdmin(@NotNull final Person person, @NotNull final Service service) {
        if (!isServiceAdmin(person, service)) {
            throw new ForbiddenException("'" + person.getLogin() + "' is not admin of service '" + service.getKey() + "'!");
        }
    }

    public static void checkServiceTrustee(@NotNull final Person person, @NotNull final Service service) {
        if (!Hierarchy.get().getServiceReader().getTrustees(service).contains(person)) {
            throw new ForbiddenException("'" + person.getLogin() + "' is not trustee of service '" + service.getKey() + "'!");
        }
    }

    public static void checkServiceAdminOrProjectResponsible(@NotNull final Person person, @NotNull final Service service,
                                                             @NotNull final Project project) {
        if (!isServiceAdmin(person, service) && !isResponsible(person, project)) {
            throw new ForbiddenException("'" + person.getLogin() + "' is not responsible of project '" + project.getKey() +
                    "' or admin of service '" + service.getKey() + "'!");
        }
    }
}
