package ru.yandex.wmtools.common.service;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;

import ru.yandex.common.framework.pager.Pager;
import ru.yandex.common.util.db.OrderByClause;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;
import ru.yandex.wmtools.common.servantlet.AbstractServantlet;
import ru.yandex.wmtools.common.util.IServiceJdbcTemplate;
import ru.yandex.wmtools.common.util.ParameterizedMapRowMapper;

/**
 * User: baton
 * Date: 24.08.2007
 * Time: 13:54:38
 */
public class SafeReplicationJdbcTemplate implements IService, IServiceJdbcTemplate {
    private static final Logger log = LoggerFactory.getLogger(SafeReplicationJdbcTemplate.class);

    private abstract class RequestProcessor<T> {
        public final T process() throws InternalException {
            try {
                getDbBanner().internalQueryVeto();
                T res = doQuery(mainJdbcTemplate);
                getDbBanner().internalAllRight();
                return res;
            } catch (InternalException e) {
                InternalProblem code = e.getProblem();

                if ((code != null) && (code.isDbError())) {
                    if (InternalProblem.MASTER_DB_BANNED.equals(code)) {
                        log.warn(AbstractServantlet.getDbIndexMessageFromExtraParam(
                                e) + "Master DB banned. Trying to take info from reserve DB", e);
                    } else {
                        log.warn("Error accessing main DB. Trying to take info from reserve DB", e);
                        getDbBanner().internalQueryFailed();
                    }

                    if (reserveJdbcTemplate != null) {
                        log.info("Taking info from reserve DB");
                        return doQuery(reserveJdbcTemplate);
                    } else {
                        log.error("RESERVE DATA SOURCE IS NULL!");
                        throw e;
                    }
                }
                throw e;
            }
        }

        abstract protected T doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException;
    }

    private DBBanner dbBanner;

    protected final DBBanner getDbBanner() {
        return dbBanner;
    }

    private final IServiceJdbcTemplate mainJdbcTemplate;
    private IServiceJdbcTemplate reserveJdbcTemplate;

    public SafeReplicationJdbcTemplate(IServiceJdbcTemplate mainJdbcTemplate, IServiceJdbcTemplate reserveJdbcTemplate,
                                       DBBanner dbBanner) {
        this.mainJdbcTemplate = mainJdbcTemplate;
        this.reserveJdbcTemplate = reserveJdbcTemplate;
        this.dbBanner = dbBanner;
    }

    @Override
    public JdbcOperations getJdbcOperations() throws InternalException {
        return new RequestProcessor<JdbcOperations>() {
            @Override
            protected JdbcOperations doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.getJdbcOperations();
            }
        }.process();
    }

    @Override
    public int queryForInt(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<Integer>() {
            @Override
            protected Integer doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.queryForInt(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public long queryForLong(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<Long>() {
            @Override
            protected Long doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.queryForLong(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public <T> T queryForObject(final String sqlString, final Class<T> aClass, final Object... requestParams)
            throws InternalException {
        return new RequestProcessor<T>() {
            @Override
            protected T doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.queryForObject(sqlString, aClass, requestParams);
            }
        }.process();
    }

    @Override
    public <T> T queryForObject(final String sqlString, final ParameterizedRowMapper<T> parameterizedRowMapper,
                                final Object... requestParams) throws InternalException {
        return new RequestProcessor<T>() {
            @Override
            protected T doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.queryForObject(sqlString, parameterizedRowMapper, requestParams);
            }
        }.process();
    }

    @Override
    public <T> List<T> query(final String sqlString, final ParameterizedRowMapper<T> parameterizedRowMapper,
                             final Object... requestParams) throws InternalException {
        return new RequestProcessor<List<T>>() {
            @Override
            protected List<T> doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.query(sqlString, parameterizedRowMapper, requestParams);
            }
        }.process();
    }

    @Override
    public Map<String, Object> queryForMap(final String sqlString, final Object... requestParams)
            throws InternalException {
        return new RequestProcessor<Map<String, Object>>() {
            @Override
            protected Map<String, Object> doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.queryForMap(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public List<Map<String, Object>> queryForList(final String sqlString, final Object... requestParams)
            throws InternalException {
        return new RequestProcessor<List<Map<String, Object>>>() {
            @Override
            protected List<Map<String, Object>> doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.queryForList(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public int update(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<Integer>() {
            @Override
            protected Integer doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.update(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public Number insertSingle(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<Number>() {
            @Override
            protected Number doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.insertSingle(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public Date safeQueryForTimestamp(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<Date>() {
            @Override
            protected Date doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.safeQueryForTimestamp(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public Integer safeQueryForInt(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<Integer>() {
            @Override
            protected Integer doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.safeQueryForInt(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public Long safeQueryForLong(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<Long>() {
            @Override
            protected Long doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.safeQueryForLong(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public String safeQueryForString(final String sqlString, final Object... requestParams) throws InternalException {
        return new RequestProcessor<String>() {
            @Override
            protected String doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.safeQueryForString(sqlString, requestParams);
            }
        }.process();
    }

    @Override
    public <T> T safeQueryForObject(final String sqlString, final ParameterizedRowMapper<T> parameterizedRowMapper,
                                    final Object... requestParams) throws InternalException {
        return new RequestProcessor<T>() {
            @Override
            protected T doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.safeQueryForObject(sqlString, parameterizedRowMapper, requestParams);
            }
        }.process();
    }

    @Override
    public <K, V> NavigableMap<K, V> queryForNavigableMap(final String sqlString,
                                                          final ParameterizedMapRowMapper<K, V> parameterizedRowMapper, final Object... requestParams)
            throws InternalException {
        return new RequestProcessor<NavigableMap<K, V>>() {
            @Override
            protected NavigableMap<K, V> doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.queryForNavigableMap(sqlString, parameterizedRowMapper, requestParams);
            }
        }.process();
    }

    @Override
    public void query(final String sqlString, final RowCallbackHandler rch, final Object... requestParams)
            throws InternalException {
        new RequestProcessor() {
            @Override
            protected Object doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                jdbcTemplate.query(sqlString, rch, requestParams);
                return null;
            }
        }.process();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> List<T> select(final String countSql, final String selectSql, final ParameterizedRowMapper<T> mapper,
                              final OrderByClause order, final Pager pager, final Object... params) throws InternalException {
        return new RequestProcessor<List<T>>() {
            @Override
            protected List<T> doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.select(countSql, selectSql, mapper, order, pager, params);
            }
        }.process();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> List<T> pageableSelect(final String countSql, final String selectSql,
                                      final ParameterizedRowMapper<T> mapper, final Pager pager, final Object... params) throws InternalException {
        return new RequestProcessor<List<T>>() {
            @Override
            protected List<T> doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.pageableSelect(countSql, selectSql, mapper, pager, params);
            }
        }.process();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> List<T> orderableSelect(final String selectSql, final ParameterizedRowMapper<T> mapper,
                                       final OrderByClause order, final Object... params) throws InternalException {
        return new RequestProcessor<List<T>>() {
            @Override
            protected List<T> doQuery(IServiceJdbcTemplate jdbcTemplate) throws InternalException {
                return jdbcTemplate.orderableSelect(selectSql, mapper, order, params);
            }
        }.process();
    }

    public boolean isMasterBanned() {
        return getDbBanner().isBanned();
    }
}
