package com.writeoff.module.finance.repository;

import com.writeoff.module.finance.model.Payment;
import com.writeoff.module.finance.model.PaymentStatus;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import com.writeoff.security.AuthContext;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Repository
@ConditionalOnProperty(prefix = "app.repository", name = "mode", havingValue = "jdbc", matchIfMissing = true)
public class JdbcPaymentRepository implements PaymentRepository {
    private final JdbcTemplate jdbcTemplate;
    private static final RowMapper<Payment> ROW_MAPPER = (rs, n) -> new Payment(
            rs.getLong("id"),
            rs.getLong("project_id"),
            rs.getLong("meeting_id"),
            rs.getLong("amount_cent"),
            PaymentStatus.valueOf(rs.getString("payment_status"))
    );

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

    @Override
    public Payment save(Payment payment) {
        if (payment.getId() == null) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(
                        "INSERT INTO finance_payment (tenant_id, project_id, meeting_id, amount_cent, payment_status, created_by, updated_by) VALUES (?, ?, ?, ?, ?, 0, 0)",
                        Statement.RETURN_GENERATED_KEYS
                );
                ps.setLong(1, tenantId());
                ps.setLong(2, payment.getProjectId());
                ps.setLong(3, payment.getMeetingId());
                ps.setLong(4, payment.getAmountCent());
                ps.setString(5, payment.getStatus().name());
                return ps;
            }, keyHolder);
            Number key = keyHolder.getKey();
            Long id = key == null ? null : key.longValue();
            return new Payment(id, payment.getProjectId(), payment.getMeetingId(), payment.getAmountCent(), payment.getStatus());
        }
        return payment;
    }

    @Override
    public List<Payment> findAll() {
        return jdbcTemplate.query("SELECT * FROM finance_payment WHERE tenant_id=? AND is_deleted=0 ORDER BY id DESC", ROW_MAPPER, tenantId());
    }

    @Override
    public boolean isProjectLocked(Long projectId) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM finance_lock_log WHERE tenant_id=? AND project_id=? AND lock_status='LOCKED' AND is_deleted=0",
                Integer.class,
                tenantId(),
                projectId
        );
        return count != null && count > 0;
    }

    @Override
    public void lockProject(Long projectId, String reason, Long operatorUserId) {
        jdbcTemplate.update(
                "UPDATE finance_lock_log SET is_deleted=1, updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND project_id=? AND lock_status='LOCKED' AND is_deleted=0",
                operatorUserId == null ? 0L : operatorUserId,
                tenantId(),
                projectId
        );
        jdbcTemplate.update(
                "INSERT INTO finance_lock_log (tenant_id, project_id, lock_status, reason, created_by, updated_by) VALUES (?, ?, 'LOCKED', ?, ?, ?)",
                tenantId(),
                projectId,
                reason,
                operatorUserId == null ? 0L : operatorUserId,
                operatorUserId == null ? 0L : operatorUserId
        );
    }

    @Override
    public void unlockProject(Long projectId, String reason, Long operatorUserId) {
        jdbcTemplate.update(
                "UPDATE finance_lock_log SET is_deleted=1, updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND project_id=? AND lock_status='LOCKED' AND is_deleted=0",
                operatorUserId == null ? 0L : operatorUserId,
                tenantId(),
                projectId
        );
        jdbcTemplate.update(
                "INSERT INTO finance_lock_log (tenant_id, project_id, lock_status, reason, created_by, updated_by) VALUES (?, ?, 'UNLOCKED', ?, ?, ?)",
                tenantId(),
                projectId,
                reason,
                operatorUserId == null ? 0L : operatorUserId,
                operatorUserId == null ? 0L : operatorUserId
        );
    }

    @Override
    public Map<String, Object> createReconciliation(Long projectId, Long expectedAmountCent, Long operatorUserId) {
        Long actualAmountCent = jdbcTemplate.queryForObject(
                "SELECT IFNULL(SUM(amount_cent),0) FROM finance_payment WHERE tenant_id=? AND project_id=? AND is_deleted=0",
                Long.class,
                tenantId(),
                projectId
        );
        long actual = actualAmountCent == null ? 0L : actualAmountCent;
        long expected = expectedAmountCent == null ? actual : expectedAmountCent;
        long diff = actual - expected;
        String resultStatus = diff == 0 ? "MATCH" : "DIFF";
        jdbcTemplate.update(
                "INSERT INTO finance_reconciliation (tenant_id, project_id, expected_amount_cent, actual_amount_cent, diff_amount_cent, result_status, created_by, updated_by) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
                tenantId(),
                projectId,
                expected,
                actual,
                diff,
                resultStatus,
                operatorUserId == null ? 0L : operatorUserId,
                operatorUserId == null ? 0L : operatorUserId
        );
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("projectId", projectId);
        data.put("expectedAmountCent", expected);
        data.put("actualAmountCent", actual);
        data.put("diffAmountCent", diff);
        data.put("resultStatus", resultStatus);
        return data;
    }

    @Override
    public List<Map<String, Object>> listReconciliations() {
        return jdbcTemplate.query(
                "SELECT id, project_id, expected_amount_cent, actual_amount_cent, diff_amount_cent, result_status, " +
                        "DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at " +
                        "FROM finance_reconciliation WHERE tenant_id=? AND is_deleted=0 ORDER BY id DESC",
                (rs, n) -> {
                    Map<String, Object> row = new LinkedHashMap<>();
                    row.put("id", rs.getLong("id"));
                    row.put("projectId", rs.getLong("project_id"));
                    row.put("expectedAmountCent", rs.getLong("expected_amount_cent"));
                    row.put("actualAmountCent", rs.getLong("actual_amount_cent"));
                    row.put("diffAmountCent", rs.getLong("diff_amount_cent"));
                    row.put("resultStatus", rs.getString("result_status"));
                    row.put("createdAt", rs.getString("created_at"));
                    return row;
                },
                tenantId()
        );
    }

    @Override
    public List<Map<String, Object>> listLockLogs(Long projectId) {
        return jdbcTemplate.query(
                "SELECT id, project_id, lock_status, reason, DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at " +
                        "FROM finance_lock_log WHERE tenant_id=? AND (? IS NULL OR project_id=?) ORDER BY id DESC",
                (rs, n) -> {
                    Map<String, Object> row = new LinkedHashMap<>();
                    row.put("id", rs.getLong("id"));
                    row.put("projectId", rs.getLong("project_id"));
                    row.put("lockStatus", rs.getString("lock_status"));
                    row.put("reason", rs.getString("reason"));
                    row.put("createdAt", rs.getString("created_at"));
                    return row;
                },
                tenantId(),
                projectId,
                projectId
        );
    }

    private Long tenantId() {
        return AuthContext.requireTenantId();
    }
}
