package com.mzh.library.dao.impl;

import com.mzh.library.dao.UserAccountDao;
import com.mzh.library.dao.UserDao;
import com.mzh.library.entity.Role;
import com.mzh.library.entity.User;
import com.mzh.library.entity.UserSearchCriteria;
import com.mzh.library.exception.DaoException;
import com.mzh.library.util.JdbcUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcUserDao implements UserDao, UserAccountDao {
    private static final Logger LOGGER = Logger.getLogger(JdbcUserDao.class.getName());
    private static final String USER_COLUMNS = ""
        + "id, username, password_hash, display_name, role_code, active, created_at, updated_at ";

    private static final String FIND_ACTIVE_BY_USERNAME = ""
        + "SELECT " + USER_COLUMNS
        + "FROM users "
        + "WHERE username = ? AND active = 1";

    private static final String FIND_BY_ID = ""
        + "SELECT " + USER_COLUMNS
        + "FROM users "
        + "WHERE id = ?";

    private static final String FIND_BY_USERNAME = ""
        + "SELECT " + USER_COLUMNS
        + "FROM users "
        + "WHERE username = ?";

    private static final String CREATE = ""
        + "INSERT INTO users (username, password_hash, display_name, role_code, active) "
        + "VALUES (?, ?, ?, ?, ?)";

    private static final String UPDATE_BASE = ""
        + "UPDATE users "
        + "SET display_name = ?, role_code = ?, active = ? ";

    @Override
    public Optional<User> findActiveByUsername(String username) {
        LOGGER.info("Active user lookup start username=" + safeLogValue(username)
            + " usernameLength=" + length(username));
        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_ACTIVE_BY_USERNAME)) {
            statement.setString(1, username);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (!resultSet.next()) {
                    LOGGER.info("Active user lookup result=not-found username=" + safeLogValue(username));
                    return Optional.empty();
                }

                User user = mapUser(resultSet);
                LOGGER.info("Active user lookup result=found"
                    + " userId=" + user.getId()
                    + " role=" + user.getRole().getCode()
                    + " username=" + safeLogValue(username));
                return Optional.of(user);
            }
        } catch (SQLException | IllegalArgumentException ex) {
            LOGGER.log(Level.SEVERE, "Active user lookup failed username=" + safeLogValue(username), ex);
            throw new DaoException("Unable to load active user by username", ex);
        }
    }

    @Override
    public List<User> search(UserSearchCriteria criteria) {
        UserSearchCriteria normalized = criteria == null ? new UserSearchCriteria() : criteria;
        List<Object> parameters = new ArrayList<>();
        StringBuilder sql = new StringBuilder("SELECT ")
            .append(USER_COLUMNS)
            .append("FROM users ")
            .append("WHERE 1 = 1 ");

        appendKeyword(sql, parameters, normalized.getKeyword());
        if (!normalized.getRoleCode().isEmpty()) {
            sql.append("AND role_code = ? ");
            parameters.add(normalized.getRoleCode());
        }
        Boolean activeValue = normalized.getActiveValue();
        if (activeValue != null) {
            sql.append("AND active = ? ");
            parameters.add(activeValue);
        }
        sql.append("ORDER BY username, id");

        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql.toString())) {
            bind(statement, parameters);
            try (ResultSet resultSet = statement.executeQuery()) {
                List<User> users = new ArrayList<>();
                while (resultSet.next()) {
                    users.add(mapUser(resultSet));
                }
                return users;
            }
        } catch (SQLException | IllegalArgumentException ex) {
            throw new DaoException("Unable to search users", ex);
        }
    }

    @Override
    public Optional<User> findById(long id) {
        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_BY_ID)) {
            statement.setLong(1, id);
            try (ResultSet resultSet = statement.executeQuery()) {
                return resultSet.next() ? Optional.of(mapUser(resultSet)) : Optional.empty();
            }
        } catch (SQLException | IllegalArgumentException ex) {
            throw new DaoException("Unable to load user by id", ex);
        }
    }

    @Override
    public Optional<User> findByUsername(String username) {
        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_BY_USERNAME)) {
            statement.setString(1, username);
            try (ResultSet resultSet = statement.executeQuery()) {
                return resultSet.next() ? Optional.of(mapUser(resultSet)) : Optional.empty();
            }
        } catch (SQLException | IllegalArgumentException ex) {
            throw new DaoException("Unable to load user by username", ex);
        }
    }

    @Override
    public long create(Connection connection, User user) {
        try (PreparedStatement statement = connection.prepareStatement(CREATE, Statement.RETURN_GENERATED_KEYS)) {
            statement.setString(1, user.getUsername());
            statement.setString(2, user.getPasswordHash());
            statement.setString(3, user.getDisplayName());
            statement.setString(4, user.getRole().getCode());
            statement.setBoolean(5, user.isActive());
            statement.executeUpdate();

            try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
                if (generatedKeys.next()) {
                    return generatedKeys.getLong(1);
                }
            }
            throw new DaoException("Unable to read generated user id", null);
        } catch (SQLException ex) {
            throw new DaoException("Unable to create user", ex);
        }
    }

    @Override
    public boolean update(Connection connection, User user, boolean updatePassword) {
        String sql = UPDATE_BASE
            + (updatePassword ? ", password_hash = ? " : "")
            + "WHERE id = ?";

        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setString(1, user.getDisplayName());
            statement.setString(2, user.getRole().getCode());
            statement.setBoolean(3, user.isActive());
            int index = 4;
            if (updatePassword) {
                statement.setString(index++, user.getPasswordHash());
            }
            statement.setLong(index, user.getId());
            return statement.executeUpdate() == 1;
        } catch (SQLException ex) {
            throw new DaoException("Unable to update user", ex);
        }
    }

    private void appendKeyword(StringBuilder sql, List<Object> parameters, String value) {
        if (value == null || value.trim().isEmpty()) {
            return;
        }
        String filter = "%" + value.trim() + "%";
        sql.append("AND (username LIKE ? OR display_name LIKE ?) ");
        parameters.add(filter);
        parameters.add(filter);
    }

    private void bind(PreparedStatement statement, List<Object> parameters) throws SQLException {
        for (int i = 0; i < parameters.size(); i++) {
            Object value = parameters.get(i);
            if (value instanceof Boolean) {
                statement.setBoolean(i + 1, (Boolean) value);
            } else {
                statement.setString(i + 1, value.toString());
            }
        }
    }

    private User mapUser(ResultSet resultSet) throws SQLException {
        User user = new User();
        user.setId(resultSet.getLong("id"));
        user.setUsername(resultSet.getString("username"));
        user.setPasswordHash(resultSet.getString("password_hash"));
        user.setDisplayName(resultSet.getString("display_name"));
        user.setRole(Role.fromCode(resultSet.getString("role_code")));
        user.setActive(resultSet.getBoolean("active"));
        user.setCreatedAt(toLocalDateTime(resultSet.getTimestamp("created_at")));
        user.setUpdatedAt(toLocalDateTime(resultSet.getTimestamp("updated_at")));
        return user;
    }

    private LocalDateTime toLocalDateTime(Timestamp timestamp) {
        return timestamp == null ? null : timestamp.toLocalDateTime();
    }

    private int length(String value) {
        return value == null ? 0 : value.length();
    }

    private String safeLogValue(String value) {
        if (value == null) {
            return "";
        }

        StringBuilder builder = new StringBuilder();
        int limit = Math.min(value.length(), 120);
        for (int i = 0; i < limit; i++) {
            char current = value.charAt(i);
            builder.append(Character.isISOControl(current) ? '?' : current);
        }
        if (value.length() > limit) {
            builder.append("...");
        }
        return builder.toString();
    }
}
