package com.writeoff.module.project.repository;

import com.writeoff.module.project.model.Project;
import com.writeoff.module.project.model.ProjectStatus;
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.List;
import java.util.Optional;

@Repository
@ConditionalOnProperty(prefix = "app.repository", name = "mode", havingValue = "jdbc", matchIfMissing = true)
public class JdbcProjectRepository implements ProjectRepository {
    private final JdbcTemplate jdbcTemplate;
    private static final RowMapper<Project> ROW_MAPPER = (rs, n) -> {
        Project p = new Project(
            rs.getLong("id"),
            rs.getString("project_name"),
            null,
            rs.getDate("start_date") == null ? null : rs.getDate("start_date").toLocalDate(),
            rs.getDate("end_date") == null ? null : rs.getDate("end_date").toLocalDate(),
            rs.getObject("enterprise_id") == null ? null : rs.getLong("enterprise_id"),
            rs.getString("enterprise_name"),
            null,
            rs.getObject("partner_enterprise_id") == null ? null : rs.getLong("partner_enterprise_id"),
            null,
            null,
            null,
            null,
            rs.getLong("budget_cent"),
            rs.getInt("meeting_total"),
            rs.getInt("meeting_completed_count"),
            rs.getInt("allow_meeting_over_budget") == 1,
            rs.getBigDecimal("over_budget_threshold_ratio") == null ? 0.1d : rs.getBigDecimal("over_budget_threshold_ratio").doubleValue(),
            rs.getString("over_budget_approval_chain_json"),
            rs.getBigDecimal("budget_execution_ratio") == null ? 0d : rs.getBigDecimal("budget_execution_ratio").doubleValue(),
            rs.getString("risk_flags_json"),

            rs.getInt("write_off_not_started_count"),
            rs.getInt("write_off_in_progress_count"),
            rs.getInt("write_off_completed_count"),
            rs.getBigDecimal("labor_fee_ratio") == null ? 0d : rs.getBigDecimal("labor_fee_ratio").doubleValue(),
            rs.getInt("allow_project_over_budget") == 1,
            rs.getString("invoice_info"),
            rs.getString("expense_ratio_json"),
            null,
            null,
            null,
            null,
            rs.getString("terminated_reason"),
            rs.getString("freeze_reason"),
            rs.getTimestamp("archived_at") == null ? null : rs.getTimestamp("archived_at").toLocalDateTime(),
            rs.getString("key_change_log_json"),
            ProjectStatus.valueOf(rs.getString("status"))
        );
        p.setSubProjectCount(rs.getObject("sub_project_count") == null ? 0 : rs.getInt("sub_project_count"));
        p.setParentProjectId(rs.getObject("parent_project_id") == null ? null : rs.getLong("parent_project_id"));
        p.setHostEnterpriseName(rs.getString("host_enterprise_name"));
        p.setHostOwnerUsers(rs.getString("host_owner_users"));
        p.setHostExecutorUsers(rs.getString("host_executor_users"));
        p.setPartnerOwnerUsers(rs.getString("partner_owner_users"));
        p.setPartnerExecutorUsers(rs.getString("partner_executor_users"));
        p.setProjectFeeJson(rs.getString("project_fee_json"));
        p.setDeleted(rs.getInt("is_deleted") == 1);
        return p;
    };

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

    @Override
    public Project save(Project project) {
        if (project.getId() == null) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(
                        "INSERT INTO project (tenant_id, project_name, parent_project_id, start_date, end_date, host_enterprise_name, partner_enterprise_id, budget_cent, meeting_total, meeting_completed_count, allow_meeting_over_budget, over_budget_threshold_ratio, over_budget_approval_chain_json, budget_execution_ratio, risk_flags_json, write_off_not_started_count, write_off_in_progress_count, write_off_completed_count, labor_fee_ratio, allow_project_over_budget, invoice_info, expense_ratio_json, project_fee_json, terminated_reason, freeze_reason, archived_at, key_change_log_json, status, created_by, updated_by) " +
                                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                        Statement.RETURN_GENERATED_KEYS
                );
                Long operator = safeUserId();
                ps.setLong(1, tenantId());
                ps.setString(2, project.getName());
                setNullableLong(ps, 3, project.getParentProjectId());
                if (project.getStartDate() == null) {
                    ps.setNull(4, java.sql.Types.DATE);
                } else {
                    ps.setDate(4, java.sql.Date.valueOf(project.getStartDate()));
                }
                if (project.getEndDate() == null) {
                    ps.setNull(5, java.sql.Types.DATE);
                } else {
                    ps.setDate(5, java.sql.Date.valueOf(project.getEndDate()));
                }
                ps.setString(6, project.getHostEnterpriseName());
                setNullableLong(ps, 7, project.getPartnerEnterpriseId());
                ps.setLong(8, project.getBudgetCent());
                ps.setInt(9, project.getMeetingTotal());
                ps.setInt(10, project.getMeetingCompletedCount());
                ps.setInt(11, project.isAllowMeetingOverBudget() ? 1 : 0);
                ps.setBigDecimal(12, java.math.BigDecimal.valueOf(project.getOverBudgetThresholdRatio()));
                ps.setString(13, project.getOverBudgetApprovalChainJson());
                ps.setBigDecimal(14, java.math.BigDecimal.valueOf(project.getBudgetExecutionRatio()));
                ps.setString(15, project.getRiskFlagsJson());
                ps.setInt(16, project.getWriteOffNotStartedCount());
                ps.setInt(17, project.getWriteOffInProgressCount());
                ps.setInt(18, project.getWriteOffCompletedCount());
                ps.setBigDecimal(19, java.math.BigDecimal.valueOf(project.getLaborFeeRatio()));
                ps.setInt(20, project.isAllowProjectOverBudget() ? 1 : 0);
                ps.setString(21, project.getInvoiceInfo());
                ps.setString(22, project.getExpenseRatioJson());
                ps.setString(23, project.getProjectFeeJson());
                ps.setString(24, project.getTerminatedReason());
                ps.setString(25, project.getFreezeReason());
                if (project.getArchivedAt() == null) {
                    ps.setNull(26, java.sql.Types.TIMESTAMP);
                } else {
                    ps.setTimestamp(26, java.sql.Timestamp.valueOf(project.getArchivedAt()));
                }
                ps.setString(27, project.getKeyChangeLogJson());
                ps.setString(28, project.getStatus().name());
                ps.setLong(29, operator);
                ps.setLong(30, operator);
                return ps;
            }, keyHolder);
            Number key = keyHolder.getKey();
            Long id = key == null ? null : key.longValue();
            Project created = new Project(
                    id,
                    project.getName(),
                    null,
                    project.getStartDate(),
                    project.getEndDate(),
                    project.getEnterpriseId(),
                    null,
                    null,
                    project.getPartnerEnterpriseId(),
                    null,
                    null,
                    null,
                    null,
                    project.getBudgetCent(),
                    project.getMeetingTotal(),
                    project.getMeetingCompletedCount(),
                    project.isAllowMeetingOverBudget(),
                    project.getOverBudgetThresholdRatio(),
                    project.getOverBudgetApprovalChainJson(),
                    project.getBudgetExecutionRatio(),
                    project.getRiskFlagsJson(),

                    project.getWriteOffNotStartedCount(),
                    project.getWriteOffInProgressCount(),
                    project.getWriteOffCompletedCount(),
                    project.getLaborFeeRatio(),
                    project.isAllowProjectOverBudget(),
                    project.getInvoiceInfo(),
                    project.getExpenseRatioJson(),
                    null,
                    null,
                    null,
                    null,
                    project.getTerminatedReason(),
                    project.getFreezeReason(),
                    project.getArchivedAt(),
                    project.getKeyChangeLogJson(),
                    project.getStatus()
            );
            created.setSubProjectCount(project.getSubProjectCount());
            created.setParentProjectId(project.getParentProjectId());
            created.setHostEnterpriseName(project.getHostEnterpriseName());
            created.setProjectFeeJson(project.getProjectFeeJson());
            return created;
        }
        jdbcTemplate.update(
                "UPDATE project SET project_name=?, parent_project_id=?, start_date=?, end_date=?, host_enterprise_name=?, partner_enterprise_id=?, budget_cent=?, meeting_total=?, meeting_completed_count=?, allow_meeting_over_budget=?, " +
                        "over_budget_threshold_ratio=?, over_budget_approval_chain_json=?, budget_execution_ratio=?, risk_flags_json=?, write_off_not_started_count=?, write_off_in_progress_count=?, write_off_completed_count=?, labor_fee_ratio=?, allow_project_over_budget=?, invoice_info=?, expense_ratio_json=?, project_fee_json=?, terminated_reason=?, freeze_reason=?, archived_at=?, key_change_log_json=?, status=?, updated_by=? WHERE tenant_id=? AND id=?",
                project.getName(),
                project.getParentProjectId(),
                project.getStartDate() == null ? null : java.sql.Date.valueOf(project.getStartDate()),
                project.getEndDate() == null ? null : java.sql.Date.valueOf(project.getEndDate()),
                project.getHostEnterpriseName(),
                project.getPartnerEnterpriseId(),
                project.getBudgetCent(),
                project.getMeetingTotal(),
                project.getMeetingCompletedCount(),
                project.isAllowMeetingOverBudget() ? 1 : 0,
                java.math.BigDecimal.valueOf(project.getOverBudgetThresholdRatio()),
                project.getOverBudgetApprovalChainJson(),
                java.math.BigDecimal.valueOf(project.getBudgetExecutionRatio()),
                project.getRiskFlagsJson(),

                project.getWriteOffNotStartedCount(),
                project.getWriteOffInProgressCount(),
                project.getWriteOffCompletedCount(),
                java.math.BigDecimal.valueOf(project.getLaborFeeRatio()),
                project.isAllowProjectOverBudget() ? 1 : 0,
                project.getInvoiceInfo(),
                project.getExpenseRatioJson(),
                project.getProjectFeeJson(),
                project.getTerminatedReason(),
                project.getFreezeReason(),
                project.getArchivedAt() == null ? null : java.sql.Timestamp.valueOf(project.getArchivedAt()),
                project.getKeyChangeLogJson(),
                project.getStatus().name(),
                safeUserId(),
                tenantId(),
                project.getId()
        );
        return project;
    }

    @Override
    public Optional<Project> findById(Long id) {
        List<Project> list = jdbcTemplate.query(
                "SELECT p.id, p.project_name, p.parent_project_id, " +
                        "(SELECT COUNT(1) FROM project c WHERE c.tenant_id=p.tenant_id AND c.parent_project_id=p.id AND c.is_deleted=0) AS sub_project_count, " +
                        "p.start_date, p.end_date, p.partner_enterprise_id AS enterprise_id, e.enterprise_name, p.host_enterprise_name, p.partner_enterprise_id, p.budget_cent, p.meeting_total, p.meeting_completed_count, " +
                        "p.allow_meeting_over_budget, p.over_budget_threshold_ratio, p.over_budget_approval_chain_json, p.budget_execution_ratio, p.risk_flags_json, p.write_off_not_started_count, p.write_off_in_progress_count, p.write_off_completed_count, p.labor_fee_ratio, p.allow_project_over_budget, p.invoice_info, p.expense_ratio_json, p.project_fee_json, p.terminated_reason, p.freeze_reason, p.archived_at, p.key_change_log_json, p.status, " +
                        "p.is_deleted, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM sys_user su JOIN user_role ur ON su.tenant_id=ur.tenant_id AND su.id=ur.user_id JOIN role r ON ur.tenant_id=r.tenant_id AND ur.role_id=r.id WHERE su.tenant_id=p.tenant_id AND su.is_deleted=0 AND r.role_code='TENANT_ADMIN') AS host_owner_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='PROJECT_OWNER' AND b.is_deleted=0 AND su.is_deleted=0) AS host_executor_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='PROJECT_EXECUTOR' AND b.is_deleted=0 AND su.is_deleted=0) AS partner_owner_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='EXECUTOR' AND b.is_deleted=0 AND su.is_deleted=0) AS partner_executor_users " +
                        "FROM project p LEFT JOIN enterprise e ON p.tenant_id=e.tenant_id AND p.partner_enterprise_id=e.id " +
                        "WHERE p.tenant_id=? AND p.id=? AND p.is_deleted=0",
                ROW_MAPPER,
                tenantId(),
                id
        );
        return list.stream().findFirst();
    }

    @Override
    public List<Project> findAll(boolean includeDeleted) {
        String whereSql = includeDeleted ? "WHERE p.tenant_id=? " : "WHERE p.tenant_id=? AND p.is_deleted=0 ";
        return jdbcTemplate.query(
                "SELECT p.id, p.project_name, p.parent_project_id, " +
                        "(SELECT COUNT(1) FROM project c WHERE c.tenant_id=p.tenant_id AND c.parent_project_id=p.id AND c.is_deleted=0) AS sub_project_count, " +
                        "p.start_date, p.end_date, p.partner_enterprise_id AS enterprise_id, e.enterprise_name, p.host_enterprise_name, p.partner_enterprise_id, p.budget_cent, p.meeting_total, p.meeting_completed_count, " +
                        "p.allow_meeting_over_budget, p.over_budget_threshold_ratio, p.over_budget_approval_chain_json, p.budget_execution_ratio, p.risk_flags_json, p.write_off_not_started_count, p.write_off_in_progress_count, p.write_off_completed_count, p.labor_fee_ratio, p.allow_project_over_budget, p.invoice_info, p.expense_ratio_json, p.project_fee_json, p.terminated_reason, p.freeze_reason, p.archived_at, p.key_change_log_json, p.status, " +
                        "p.is_deleted, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM sys_user su JOIN user_role ur ON su.tenant_id=ur.tenant_id AND su.id=ur.user_id JOIN role r ON ur.tenant_id=r.tenant_id AND ur.role_id=r.id WHERE su.tenant_id=p.tenant_id AND su.is_deleted=0 AND r.role_code='TENANT_ADMIN') AS host_owner_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='PROJECT_OWNER' AND b.is_deleted=0 AND su.is_deleted=0) AS host_executor_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='PROJECT_EXECUTOR' AND b.is_deleted=0 AND su.is_deleted=0) AS partner_owner_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='EXECUTOR' AND b.is_deleted=0 AND su.is_deleted=0) AS partner_executor_users " +
                        "FROM project p LEFT JOIN enterprise e ON p.tenant_id=e.tenant_id AND p.partner_enterprise_id=e.id " +
                        whereSql +
                        "ORDER BY p.id DESC",
                ROW_MAPPER,
                tenantId()
        );
    }

    @Override
    public List<Project> findByParentProjectId(Long parentProjectId, boolean includeDeleted) {
        String whereSql = includeDeleted
                ? "WHERE p.tenant_id=? AND p.parent_project_id=? "
                : "WHERE p.tenant_id=? AND p.parent_project_id=? AND p.is_deleted=0 ";
        return jdbcTemplate.query(
                "SELECT p.id, p.project_name, p.parent_project_id, " +
                        "(SELECT COUNT(1) FROM project c WHERE c.tenant_id=p.tenant_id AND c.parent_project_id=p.id AND c.is_deleted=0) AS sub_project_count, " +
                        "p.start_date, p.end_date, p.partner_enterprise_id AS enterprise_id, e.enterprise_name, p.host_enterprise_name, p.partner_enterprise_id, p.budget_cent, p.meeting_total, p.meeting_completed_count, " +
                        "p.allow_meeting_over_budget, p.over_budget_threshold_ratio, p.over_budget_approval_chain_json, p.budget_execution_ratio, p.risk_flags_json, p.write_off_not_started_count, p.write_off_in_progress_count, p.write_off_completed_count, p.labor_fee_ratio, p.allow_project_over_budget, p.invoice_info, p.expense_ratio_json, p.project_fee_json, p.terminated_reason, p.freeze_reason, p.archived_at, p.key_change_log_json, p.status, " +
                        "p.is_deleted, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM sys_user su JOIN user_role ur ON su.tenant_id=ur.tenant_id AND su.id=ur.user_id JOIN role r ON ur.tenant_id=r.tenant_id AND ur.role_id=r.id WHERE su.tenant_id=p.tenant_id AND su.is_deleted=0 AND r.role_code='TENANT_ADMIN') AS host_owner_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='PROJECT_OWNER' AND b.is_deleted=0 AND su.is_deleted=0) AS host_executor_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='PROJECT_EXECUTOR' AND b.is_deleted=0 AND su.is_deleted=0) AS partner_owner_users, " +
                        "(SELECT GROUP_CONCAT(DISTINCT su.user_name SEPARATOR '、') FROM project_user_binding b JOIN sys_user su ON b.tenant_id=su.tenant_id AND b.user_id=su.id WHERE b.tenant_id=p.tenant_id AND b.project_id=p.id AND b.bind_role_code='EXECUTOR' AND b.is_deleted=0 AND su.is_deleted=0) AS partner_executor_users " +
                        "FROM project p LEFT JOIN enterprise e ON p.tenant_id=e.tenant_id AND p.partner_enterprise_id=e.id " +
                        whereSql +
                        "ORDER BY p.id DESC",
                ROW_MAPPER,
                tenantId(),
                parentProjectId
        );
    }

    private static void setNullableLong(PreparedStatement ps, int index, Long value) throws java.sql.SQLException {
        if (value == null) {
            ps.setNull(index, java.sql.Types.BIGINT);
        } else {
            ps.setLong(index, value);
        }
    }

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

    private Long safeUserId() {
        Long userId = AuthContext.userId();
        return userId == null ? 0L : userId;
    }
}
