package com.atlassian.bitbucket.hamcrest;

import liquibase.change.ColumnConfig;
import liquibase.change.core.InsertDataChange;
import liquibase.changelog.ChangeSet;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.TypeSafeMatcher;

import java.util.Date;

/**
 * A bunch of Hamcrest matchers that work on Liquibase types.
 */
public class LiquibaseMatchers {
    /** Prevents instantiation */
    private LiquibaseMatchers() {
        throw new UnsupportedOperationException("Attempt to instantiate utility class");
    }

    @Factory
    public static HasTableName hasTableName(String tableName) {
        return new HasTableName(tableName);
    }

    public static final class HasTableName extends TypeSafeMatcher<InsertDataChange> {
        private final String tableName;

        public HasTableName(String tableName) {
            this.tableName = tableName;
        }

        @Override
        public boolean matchesSafely(InsertDataChange insertDataChange) {
            return insertDataChange.getTableName().equals(tableName);
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("has table name: ").appendValue(tableName);
        }
    }

    @Factory
    public static HasDbms hasDbms(String dbms) {
        return new HasDbms(dbms);
    }

    public static final class HasDbms extends TypeSafeMatcher<ChangeSet> {
        private final String dbms;

        public HasDbms(String dbms) {
            this.dbms = dbms;
        }

        @Override
        public boolean matchesSafely(ChangeSet changeSet) {
            return changeSet.getDbmsSet().contains(dbms);
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("has dbms");
        }
    }

    @Factory
    public static <V> ColumnConfigMatcher columnConfig(String columnName, V columnValue) {
        return new ColumnConfigMatcher<>(columnName, columnValue);
    }

    public static final class ColumnConfigMatcher<V> extends TypeSafeMatcher<ColumnConfig> {
        private final String columnName;
        private final V columnValue;

        public ColumnConfigMatcher(String columnName, V columnValue) {
            this.columnName = columnName;
            this.columnValue = columnValue;
        }

        @Override
        public boolean matchesSafely(ColumnConfig columnConfig) {
            if (!columnName.equals(columnConfig.getName())) {
                return false;
            }

            if (columnValue instanceof Boolean) {
                return columnValue.equals(columnConfig.getValueBoolean());
            } else if (columnValue instanceof Date) {
                return columnValue.equals(columnConfig.getValueDate());
            } else if (columnValue instanceof Number) {
                return columnValue.equals(columnConfig.getValueNumeric());
            } else {
                return columnValue.equals(columnConfig.getValueObject());
            }
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("has name: ").appendValue(columnName)
                       .appendText(" and has value: ").appendValue(columnValue);
        }
    }

}
