package com.atlassian.bitbucket.internal.mirroring.mirror.rest.cloud;

import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.rest.RestMapEntity;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Map;

import static com.atlassian.bitbucket.internal.mirroring.mirror.rest.cloud.RestCloudEntityProperties.*;
import static com.atlassian.bitbucket.repository.RefChangeType.*;

public class RestCloudExternalRefChange extends RestMapEntity implements RefChange {

    public RestCloudExternalRefChange(Map<String, ?> values) {
        super(values);
    }

    @Nonnull
    @Override
    public String getFromHash() {
        switch (getType()) {
            case ADD:
                return EMPTY_HASH;
            case DELETE:
                // fall through
            case UPDATE:
                return (String) getNestedMap(OLD, TARGET).get(HASH);
            default:
                throw new IllegalStateException("Unknown ref type: " + getType());
        }
    }

    @Nonnull
    @Override
    public MinimalRef getRef() {
        Map<String, ?> refSubject = getRefSubject();
        StandardRefType refType = getRefType(refSubject);

        return new SimpleMinimalRef.Builder()
                .type(refType)
                .id((String) refSubject.get(NAME))
                .displayId((String) refSubject.get(NAME))
                .build();
    }

    @Nonnull
    @Override
    public String getRefId() {
        return getRef().getId();
    }

    @Nonnull
    @Override
    public String getToHash() {
        switch (getType()) {
            case ADD:
                // fall through
            case UPDATE:
                return String.class.cast(getNestedMap(NEW, TARGET).get(HASH));
            case DELETE:
                return EMPTY_HASH;
            default:
                throw new IllegalStateException("Unknown ref type: " + getType());
        }
    }

    @Nonnull
    @Override
    public RefChangeType getType() {
        if (getBoolProperty(CREATED, false)) {
            return ADD;
        } else if (getBoolProperty(CLOSED, false)) {
            return DELETE;
        } else {
            return UPDATE;
        }
    }

    @SuppressWarnings("unchecked")
    protected Map<String, ?> getNestedMap(String... keys) {
        Map<String, ?> current = this;
        for (String key : keys) {
            current = (Map<String, ?>) current.get(key);
            if (current == null) {
                throw new IllegalStateException("Property " + key + " not found");
            }
        }
        return current;
    }

    @SuppressWarnings("unchecked")
    protected Map<String, ?> getRefSubject() {
        Map<String, ?> refSubject = Collections.emptyMap();
        switch (getType()) {
            case ADD:
                refSubject = (Map<String, ?>) get(NEW);
                break;
            case DELETE:
            case UPDATE:
                refSubject = (Map<String, ?>) get(OLD);
                break;
        }
        return refSubject;
    }

    protected StandardRefType getRefType(Map<String, ?> refSubject) {
        // in Hg, changes to tags are only ever expressed as commits on the tags file on a branch
        // so for BBC the changes array in the payload will only ever have branch changes, not tags changes
        return StandardRefType.BRANCH;
    }

    @SuppressWarnings("unchecked")
    public static RestCloudExternalRefChange valueOf(Object value) {
        if (value instanceof RestCloudExternalRefChange) {
            return (RestCloudExternalRefChange) value;
        } else if (value instanceof Map<?, ?>) {
            return new RestCloudExternalRefChange((Map<String, ?>) value);
        } else {
            return null;
        }
    }
}