package com.zcloud.personnel.positioning.command;

import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Component
public class FindsSyncStateService {
    private static final String STATUS_SUCCESS = "SUCCESS";

    private final JdbcTemplate jdbcTemplate;

    public FindsSyncStateService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public boolean completeFence(FenceDO snapshot, Long findsId, String findsCode, String rawJson) {
        int updated = guardedUpdate("fence", snapshot.getId(), snapshot.getSyncToken(), false,
                "finds_fence_id = ?, finds_fence_code = ?, sync_status = ?, last_sync_time = ?, raw_json = ?",
                findsId, findsCode, STATUS_SUCCESS, LocalDateTime.now(), rawJson);
        if (updated == 0) {
            recordFenceMapping(snapshot.getId(), findsId, findsCode);
        }
        return updated > 0;
    }

    public boolean completeAlarmRule(AlarmRuleDO snapshot, Long findsId, String rawJson) {
        int updated = guardedUpdate("alarm_rule", snapshot.getId(), snapshot.getSyncToken(), false,
                "finds_rule_id = ?, sync_status = ?, last_sync_time = ?, raw_json = ?",
                findsId, STATUS_SUCCESS, LocalDateTime.now(), rawJson);
        if (updated == 0) {
            recordAlarmRuleMapping(snapshot.getId(), findsId);
        }
        return updated > 0;
    }

    public boolean completeTerminal(TerminalDO snapshot, Long findsId, String rawJson) {
        int updated = guardedUpdate("terminal", snapshot.getId(), snapshot.getSyncToken(), false,
                "finds_terminal_id = ?, sync_status = ?, last_sync_time = ?, raw_json = ?",
                findsId, STATUS_SUCCESS, LocalDateTime.now(), rawJson);
        if (updated == 0) {
            recordTerminalMapping(snapshot.getId(), findsId);
        }
        return updated > 0;
    }

    public boolean completeTerminalBinding(TerminalBindDO snapshot, Long findsTerminalId) {
        return guardedUpdate("terminal_bind", snapshot.getId(), snapshot.getSyncToken(), false,
                "finds_terminal_id = ?, sync_status = ?, last_sync_time = ?",
                findsTerminalId, STATUS_SUCCESS, LocalDateTime.now()) > 0;
    }

    public void failFence(FenceDO snapshot, boolean deleting, String rawJson) {
        guardedFailure("fence", snapshot.getId(), snapshot.getSyncToken(), deleting, rawJson);
    }

    public void failAlarmRule(AlarmRuleDO snapshot, boolean deleting, String rawJson) {
        guardedFailure("alarm_rule", snapshot.getId(), snapshot.getSyncToken(), deleting, rawJson);
    }

    public void failTerminal(TerminalDO snapshot, boolean deleting, String rawJson) {
        guardedFailure("terminal", snapshot.getId(), snapshot.getSyncToken(), deleting, rawJson);
    }

    public void failTerminalBinding(TerminalBindDO snapshot) {
        guardedFailure("terminal_bind", snapshot.getId(), snapshot.getSyncToken(), false, null);
    }

    public void recordFenceMapping(Long id, Long findsId, String findsCode) {
        if (id == null || findsId == null) {
            return;
        }
        jdbcTemplate.update("UPDATE fence SET finds_fence_id = COALESCE(finds_fence_id, ?), "
                + "finds_fence_code = COALESCE(finds_fence_code, ?) WHERE id = ?", findsId, findsCode, id);
    }

    public void recordAlarmRuleMapping(Long id, Long findsId) {
        if (id == null || findsId == null) {
            return;
        }
        jdbcTemplate.update("UPDATE alarm_rule SET finds_rule_id = COALESCE(finds_rule_id, ?) WHERE id = ?", findsId, id);
    }

    public void recordTerminalMapping(Long id, Long findsId) {
        if (id == null || findsId == null) {
            return;
        }
        jdbcTemplate.update("UPDATE terminal SET finds_terminal_id = COALESCE(finds_terminal_id, ?) WHERE id = ?", findsId, id);
    }

    public boolean clearFenceMapping(FenceDO snapshot, Long expectedFindsId) {
        return snapshot != null && guardedClearMapping("fence", "finds_fence_id = NULL, finds_fence_code = NULL",
                "finds_fence_id", snapshot.getId(), snapshot.getSyncToken(), expectedFindsId) > 0;
    }

    public boolean clearAlarmRuleMapping(AlarmRuleDO snapshot, Long expectedFindsId) {
        return snapshot != null && guardedClearMapping("alarm_rule", "finds_rule_id = NULL", "finds_rule_id",
                snapshot.getId(), snapshot.getSyncToken(), expectedFindsId) > 0;
    }

    public boolean clearTerminalMapping(TerminalDO snapshot, Long expectedFindsId) {
        return snapshot != null && guardedClearMapping("terminal", "finds_terminal_id = NULL", "finds_terminal_id",
                snapshot.getId(), snapshot.getSyncToken(), expectedFindsId) > 0;
    }

    public boolean isCurrentFence(FenceDO snapshot) {
        return snapshot != null && isCurrentChange("fence", snapshot.getId(), snapshot.getSyncToken());
    }

    public boolean isCurrentAlarmRule(AlarmRuleDO snapshot) {
        return snapshot != null && isCurrentChange("alarm_rule", snapshot.getId(), snapshot.getSyncToken());
    }

    public boolean isCurrentTerminal(TerminalDO snapshot) {
        return snapshot != null && isCurrentChange("terminal", snapshot.getId(), snapshot.getSyncToken());
    }

    public void recordAreaFenceMapping(Long areaId, Long fenceId, Long findsFenceId) {
        if (areaId == null || findsFenceId == null) {
            return;
        }
        jdbcTemplate.update("UPDATE area SET bind_fence_id = ?, finds_fence_id = ? WHERE id = ?",
                fenceId, findsFenceId, areaId);
    }

    public boolean deleteFenceIfCurrent(FenceDO snapshot) {
        return guardedDelete("fence", snapshot.getId(), snapshot.getSyncToken()) > 0;
    }

    public boolean deleteAlarmRuleIfCurrent(AlarmRuleDO snapshot) {
        return guardedDelete("alarm_rule", snapshot.getId(), snapshot.getSyncToken()) > 0;
    }

    public boolean deleteTerminalIfCurrent(TerminalDO snapshot) {
        return guardedDelete("terminal", snapshot.getId(), snapshot.getSyncToken()) > 0;
    }

    private void guardedFailure(String table, Long id, String token, boolean deleting, String rawJson) {
        String status = deleting ? FindsPendingSyncService.STATUS_DELETE_FAIL : FindsPendingSyncService.STATUS_FAIL;
        if (rawJson == null) {
            guardedUpdate(table, id, token, deleting,
                    "sync_status = ?, last_sync_time = ?", status, LocalDateTime.now());
            return;
        }
        guardedUpdate(table, id, token, deleting,
                "sync_status = ?, last_sync_time = ?, raw_json = ?", status, LocalDateTime.now(), rawJson);
    }

    private int guardedUpdate(String table, Long id, String token, boolean deleting,
                              String setClause, Object... setArgs) {
        List<Object> args = new ArrayList<>();
        for (Object setArg : setArgs) {
            args.add(setArg);
        }
        args.add(id);
        StringBuilder sql = new StringBuilder("UPDATE ").append(table).append(" SET ")
                .append(setClause).append(" WHERE id = ? AND sync_status IN (?, ?) AND ");
        if (deleting) {
            args.add(FindsPendingSyncService.STATUS_DELETE_WAIT);
            args.add(FindsPendingSyncService.STATUS_DELETE_FAIL);
        } else {
            args.add(FindsPendingSyncService.STATUS_WAIT);
            args.add(FindsPendingSyncService.STATUS_FAIL);
        }
        appendTokenCondition(sql, args, token);
        return jdbcTemplate.update(sql.toString(), args.toArray());
    }

    private int guardedDelete(String table, Long id, String token) {
        List<Object> args = new ArrayList<>();
        args.add(id);
        StringBuilder sql = new StringBuilder("DELETE FROM ").append(table)
                .append(" WHERE id = ? AND sync_status IN (?, ?) AND ");
        args.add(FindsPendingSyncService.STATUS_DELETE_WAIT);
        args.add(FindsPendingSyncService.STATUS_DELETE_FAIL);
        appendTokenCondition(sql, args, token);
        return jdbcTemplate.update(sql.toString(), args.toArray());
    }

    private int guardedClearMapping(String table, String setClause, String mappingColumn,
                                    Long id, String token, Long expectedFindsId) {
        if (id == null || expectedFindsId == null) {
            return 0;
        }
        List<Object> args = new ArrayList<>();
        args.add(id);
        args.add(expectedFindsId);
        args.add(FindsPendingSyncService.STATUS_WAIT);
        args.add(FindsPendingSyncService.STATUS_FAIL);
        StringBuilder sql = new StringBuilder("UPDATE ").append(table).append(" SET ").append(setClause)
                .append(" WHERE id = ? AND ").append(mappingColumn)
                .append(" = ? AND sync_status IN (?, ?) AND ");
        appendTokenCondition(sql, args, token);
        return jdbcTemplate.update(sql.toString(), args.toArray());
    }

    private boolean isCurrentChange(String table, Long id, String token) {
        if (id == null) {
            return false;
        }
        List<Object> args = new ArrayList<>();
        args.add(id);
        args.add(FindsPendingSyncService.STATUS_WAIT);
        args.add(FindsPendingSyncService.STATUS_FAIL);
        StringBuilder sql = new StringBuilder("SELECT COUNT(1) FROM ").append(table)
                .append(" WHERE id = ? AND sync_status IN (?, ?) AND ");
        appendTokenCondition(sql, args, token);
        Long count = jdbcTemplate.queryForObject(sql.toString(), args.toArray(), Long.class);
        return count != null && count > 0;
    }

    private void appendTokenCondition(StringBuilder sql, List<Object> args, String token) {
        if (token == null) {
            sql.append("sync_token IS NULL");
        } else {
            sql.append("sync_token = ?");
            args.add(token);
        }
    }
}
