package ru.yandex.wmconsole.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;

import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;

import ru.yandex.wmconsole.data.AvailableEnum;
import ru.yandex.wmconsole.data.info.AvailableInfo;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.info.HostDbHostInfo;
import ru.yandex.wmconsole.data.info.HostUpdatesInfo;
import ru.yandex.wmconsole.data.info.OfflineLinksInfo;
import ru.yandex.wmconsole.data.info.UsersHostsOptionsInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.service.dao.TblHostUpdatesDao;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.service.AbstractDbService;
import ru.yandex.wmtools.common.util.Md5Util;
import ru.yandex.wmtools.common.util.SqlUtil;

/**
 * Created by IntelliJ IDEA.
 * User: senin
 * Date: 30.11.2007
 * Time: 20:03:49
 */
public class OfflineLinksService extends AbstractDbService {
    private static final Logger log = LoggerFactory.getLogger(OfflineLinksService.class);

    private static final String FIELD_OFFLINE_LINKS_UPDATED = "offline_links_updated";
    private static final String FIELD_OFFLINE_LINKS_SIZE = "offline_links_size";
    private static final String FIELD_OFFLINE_LINKS_LAST_ACCESS = "offline_links_last_access";
    private static final String FIELD_SECRET_STRING = "secret_string";
    private static final String FIELD_ENABLED = "offline_links_enabled";
    private static final String FIELD_REQUEST_DATE = "request_date";

    private static final String SELECT_USERS_HOSTS_OPTIONS_QUERY =
            "SELECT " +
                    "    offline_%s_last_access AS " + FIELD_OFFLINE_LINKS_LAST_ACCESS + " " +
                    "FROM " +
                    "    tbl_users_hosts_options " +
                    "WHERE " +
                    "    user_id = ? " +
                    "AND " +
                    "    host_id = ?";

    private static final String SELECT_OFFLINE_LINKS_OPTION_QUERY =
            "SELECT " +
                    "    offline_link_last_access AS " + FIELD_OFFLINE_LINKS_LAST_ACCESS + ", " +
                    "    offline_links_enabled AS " + FIELD_ENABLED + "," +
                    "    request_date AS " + FIELD_REQUEST_DATE + " " +
                    "FROM " +
                    "    tbl_users_hosts_options " +
                    "WHERE " +
                    "    user_id = ? " +
                    "AND " +
                    "    host_id = ?";

    private static final String INSERT_USERS_HOSTS_OPTIONS_OFFLINE_LAST_ACCESS_QUERY =
            "INSERT INTO " +
                    "    tbl_users_hosts_options(user_id, host_id, offline_%1$s_last_access) " +
                    "VALUES " +
                    "    (?, ?, NOW()) " +
                    "ON DUPLICATE KEY UPDATE " +
                    "    offline_%1$s_last_access = NOW()";

    private static final String USERS_HOSTS_OPTIONS_OFFLINE_LINKS_ENABLED_QUERY =
            "INSERT INTO " +
                    "   tbl_users_hosts_options (user_id, host_id, offline_links_enabled, request_date) " +
                    "VALUES " +
                    "   (?, ?, ?, NOW()) " +
                    "ON DUPLICATE KEY UPDATE " +
                    "   offline_links_enabled = ?, " +
                    "   request_date = NOW() ";

    private static final String SELECT_SECRET_STRING_QUERY =
            "SELECT " +
                    "    name AS " + FIELD_SECRET_STRING + " " +
                    "FROM " +
                    "    tbl_secret_string " +
                    "ORDER BY " +
                    "    id DESC " +
                    "LIMIT " +
                    "    1 ";

    private HostDbHostInfoService hostDbHostInfoService;
    private IWMCUserInfoService userInfoService;
    private HostInfoService hostInfoService;
    private TblHostUpdatesDao tblHostUpdatesDao;

    private String serverOfflineLinks;

    public AvailableInfo isNewAvailable(long userId, long realUserId, BriefHostInfo hostInfo, LinkType linkType)
            throws UserException, InternalException {
        if (!userInfoService.getUserOptions(userId).first) {
            return AvailableInfo.createOptionDisabled();
        }

        HostDbHostInfo hostDbHostInfo = hostDbHostInfoService.getHostDbHostInfo(hostInfo.getName());
        HostUpdatesInfo hostUpdatesInfo = tblHostUpdatesDao.getHostUpdatesInfo(hostDbHostInfo, linkType);
        if ((hostUpdatesInfo == null) || (hostUpdatesInfo.getOfflineLinksUpdated() == null)) {
            return AvailableInfo.createNotReady();
        }

        final DateTime updated = new DateTime(hostUpdatesInfo.getOfflineLinksUpdated());
        final DateTime thirtyDaysAgo = new DateTime().minusDays(30);
        if (updated.isBefore(thirtyDaysAgo)) {
            return AvailableInfo.createNotReady();
        }

        String filename = generateUrl(realUserId, hostInfo.getId(), linkType);

        UsersHostsOptionsInfo usersHostsOptionsInfo = getJdbcTemplate(new WMCPartition(null, userId)).safeQueryForObject(String.format(
                SELECT_USERS_HOSTS_OPTIONS_QUERY, linkType.getName()), usersHostsOptionsInfoMapper, userId, hostInfo.getId());
        if ((usersHostsOptionsInfo == null) || (usersHostsOptionsInfo.getOfflineLinksLastAccess() == null)) {
            return new AvailableInfo(AvailableEnum.NEW, filename, hostUpdatesInfo.getOfflineLinksSize(), hostUpdatesInfo.getOfflineLinksUpdated());
        }

        if (hostUpdatesInfo.getOfflineLinksUpdated().after(usersHostsOptionsInfo.getOfflineLinksLastAccess())) {
            return new AvailableInfo(AvailableEnum.NEW, filename, hostUpdatesInfo.getOfflineLinksSize(), hostUpdatesInfo.getOfflineLinksUpdated());
        } else {
            return new AvailableInfo(AvailableEnum.OLD, filename, hostUpdatesInfo.getOfflineLinksSize(), hostUpdatesInfo.getOfflineLinksUpdated());
        }
    }

    public OfflineLinksInfo getOfflineLinksInfo(long userId, long realUserId, BriefHostInfo hostInfo, LinkType linkType) throws InternalException {
        if (!LinkType.LINKS.equals(linkType)) {
            return OfflineLinksInfo.createOptionDisabled();
        }

        final List<OfflineLinksOptions> options = getJdbcTemplate(WMCPartition.nullPartition()).query(
                SELECT_OFFLINE_LINKS_OPTION_QUERY, offlineLinksOptionsMapper, userId, hostInfo.getId());
        final OfflineLinksOptions option;
        if (options.isEmpty()) {
            option = new OfflineLinksOptions(false, null, null);
        } else  {
            option = options.iterator().next();
        }

        HostDbHostInfo hostDbHostInfo = hostDbHostInfoService.getHostDbHostInfo(hostInfo.getName());
        HostUpdatesInfo hostUpdatesInfo = tblHostUpdatesDao.getHostUpdatesInfo(hostDbHostInfo, linkType);
        if ((hostUpdatesInfo == null) || (hostUpdatesInfo.getOfflineLinksUpdated() == null)) {
            if (option.getEnabled()) {
                return OfflineLinksInfo.createNotReady(option.getEnabled(), option.getRequestDate());
            } else {
                return OfflineLinksInfo.createOptionDisabled();
            }
        }

        final DateTime updated = new DateTime(hostUpdatesInfo.getOfflineLinksUpdated());
        final DateTime thirtyDaysAgo = new DateTime().minusDays(30);
        if (updated.isBefore(thirtyDaysAgo)) {
            if (option.getEnabled()) {
                return OfflineLinksInfo.createNotReady(option.getEnabled(), option.getRequestDate());
            } else {
                return OfflineLinksInfo.createOptionDisabled();
            }
        }

        String filename = generateUrl(realUserId, hostInfo.getId(), linkType);

        if (option.getLastAccess() == null) {
            return new OfflineLinksInfo(
                    AvailableEnum.NEW, option.getEnabled(), option.getRequestDate(), filename, hostUpdatesInfo.getOfflineLinksSize(),
                    hostUpdatesInfo.getOfflineLinksUpdated());
        }

        if (hostUpdatesInfo.getOfflineLinksUpdated().after(option.getLastAccess())) {
            return new OfflineLinksInfo(AvailableEnum.NEW, option.getEnabled(), option.getRequestDate(), filename,
                    hostUpdatesInfo.getOfflineLinksSize(), hostUpdatesInfo.getOfflineLinksUpdated());
        } else {
            return new OfflineLinksInfo(AvailableEnum.OLD, option.getEnabled(), option.getRequestDate(), filename,
                    hostUpdatesInfo.getOfflineLinksSize(), hostUpdatesInfo.getOfflineLinksUpdated());
        }
    }


    public static class OfflineLinksOptions {
        Date lastAccess;
        Boolean enabled;
        Date requestDate;

        public OfflineLinksOptions(Boolean enabled, Date requestDate, Date lastAccess) {
            this.lastAccess = lastAccess;
            this.enabled = enabled;
            this.requestDate = requestDate;
        }

        public Date getLastAccess() {
            return lastAccess;
        }

        public @NotNull Boolean getEnabled() {
            return (enabled != null) ? enabled : false;
        }

        public Date getRequestDate() {
            return requestDate;
        }
    }

    public List<String> getSecretStringList() throws InternalException {
        ParameterizedRowMapper<String> secretStringRowMapper = new ParameterizedRowMapper<String>() {
            @Override
            public String mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
                return resultSet.getString(FIELD_SECRET_STRING);
            }
        };

        return getJdbcTemplate(WMCPartition.nullPartition()).query(SELECT_SECRET_STRING_QUERY, secretStringRowMapper);
    }

    public String getSecretString() throws InternalException {
        List<String> secretStringList = getSecretStringList();
        if ((secretStringList == null) || (secretStringList.size() == 0)) {
            throw new InternalException(InternalProblem.SECRET_STRING_NOT_FOUND, "Secret string not found");
        }

        log.debug("SecretString: " + secretStringList.get(0));
        return secretStringList.get(0);
    }

    public String generateUrl(long realUserId, long hostId, LinkType linkType) throws InternalException {
        String loHostname = hostInfoService.getHostNameByHostId(hostId).toLowerCase();

        StringBuilder info = new StringBuilder();
        info.append(loHostname).append(".").append(realUserId).append(".").append(getSecretString());
        log.debug("GenerateUrl. info = " + info.toString());

        String md5 = Md5Util.getMd5(info);

        return serverOfflineLinks + "/" + linkType.getName() + "/" + loHostname.replace("https://","https_") + "." + md5 + ".gz";
    }

    public void userDownloaded(long userId, final BriefHostInfo hostInfo, LinkType linkType) {
        try {
            getJdbcTemplate(new WMCPartition(null, userId)).update(String.format(
                    INSERT_USERS_HOSTS_OPTIONS_OFFLINE_LAST_ACCESS_QUERY, linkType.getName()), userId, hostInfo.getId());
        } catch (InternalException e) {
            log.warn("Download statistics were not saved into DB.", e);
        }
    }

    public void updateOfflineLinksOption(long userId, long hostId, boolean offlineLinksEnabled) throws InternalException {
        getJdbcTemplate(WMCPartition.nullPartition()).update(
                USERS_HOSTS_OPTIONS_OFFLINE_LINKS_ENABLED_QUERY, userId, hostId, offlineLinksEnabled, offlineLinksEnabled);
    }

    private static final ParameterizedRowMapper<UsersHostsOptionsInfo> usersHostsOptionsInfoMapper =
            new ParameterizedRowMapper<UsersHostsOptionsInfo>() {
                @Override
                public UsersHostsOptionsInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Date offlineLinkLastAccess = SqlUtil.safeGetTimestamp(rs, FIELD_OFFLINE_LINKS_LAST_ACCESS);
                    return new UsersHostsOptionsInfo(offlineLinkLastAccess);
                }
            };

    private static final ParameterizedRowMapper<OfflineLinksOptions> offlineLinksOptionsMapper =
            new ParameterizedRowMapper<OfflineLinksOptions>() {
                @Override
                public OfflineLinksOptions mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Boolean enabled = SqlUtil.getBooleanNullable(rs, FIELD_ENABLED);
                    Date requestDate = SqlUtil.getDateNullable(rs, FIELD_REQUEST_DATE);
                    Date lastAccess = SqlUtil.getDateNullable(rs, FIELD_OFFLINE_LINKS_LAST_ACCESS);
                    return new OfflineLinksOptions(enabled, requestDate, lastAccess);
                }
            };

    @Required
    public void setHostDbHostInfoService(HostDbHostInfoService hostDbHostInfoService) {
        this.hostDbHostInfoService = hostDbHostInfoService;
    }

    @Required
    public void setUserInfoService(IWMCUserInfoService userInfoService) {
        this.userInfoService = userInfoService;
    }

    @Required
    public void setHostInfoService(HostInfoService hostInfoService) {
        this.hostInfoService = hostInfoService;
    }

    @Required
    public void setServerOfflineLinks(String serverOfflineLinks) {
        this.serverOfflineLinks = serverOfflineLinks;
    }

    @Required
    public void setTblHostUpdatesDao(TblHostUpdatesDao tblHostUpdatesDao) {
        this.tblHostUpdatesDao = tblHostUpdatesDao;
    }
}
