package com.cameleer.server.app.storage;

import com.cameleer.server.core.security.UserInfo;
import com.cameleer.server.core.security.UserRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

@Repository
public class PostgresUserRepository implements UserRepository {

    private final JdbcTemplate jdbc;

    public PostgresUserRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    private static final String USER_COLUMNS =
            "user_id, provider, email, display_name, created_at, failed_login_attempts, locked_until, token_revoked_before";

    @Override
    public Optional<UserInfo> findById(String userId) {
        var results = jdbc.query(
                "SELECT " + USER_COLUMNS + " FROM users WHERE user_id = ?",
                (rs, rowNum) -> mapUser(rs), userId);
        return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
    }

    @Override
    public List<UserInfo> findAll() {
        return jdbc.query("SELECT " + USER_COLUMNS + " FROM users ORDER BY user_id",
                (rs, rowNum) -> mapUser(rs));
    }

    @Override
    public void upsert(UserInfo user) {
        jdbc.update("""
                INSERT INTO users (user_id, provider, email, display_name, created_at, updated_at)
                VALUES (?, ?, ?, ?, now(), now())
                ON CONFLICT (user_id) DO UPDATE SET
                    provider = EXCLUDED.provider, email = EXCLUDED.email,
                    display_name = EXCLUDED.display_name,
                    updated_at = now()
                """,
                user.userId(), user.provider(), user.email(), user.displayName());
    }

    @Override
    public void delete(String userId) {
        jdbc.update("DELETE FROM users WHERE user_id = ?", userId);
    }

    @Override
    public void setPassword(String userId, String passwordHash) {
        jdbc.update("UPDATE users SET password_hash = ? WHERE user_id = ?", passwordHash, userId);
    }

    @Override
    public Optional<String> getPasswordHash(String userId) {
        List<String> results = jdbc.query(
                "SELECT password_hash FROM users WHERE user_id = ?",
                (rs, rowNum) -> rs.getString("password_hash"),
                userId);
        if (results.isEmpty() || results.get(0) == null) return Optional.empty();
        return Optional.of(results.get(0));
    }

    @Override
    public void recordFailedLogin(String userId) {
        jdbc.update("""
                UPDATE users
                SET failed_login_attempts = failed_login_attempts + 1,
                    locked_until = CASE
                        WHEN failed_login_attempts + 1 >= 5
                            THEN now() + INTERVAL '15 minutes'
                        ELSE locked_until
                    END
                WHERE user_id = ?
                """, userId);
    }

    @Override
    public void clearFailedLogins(String userId) {
        jdbc.update("UPDATE users SET failed_login_attempts = 0, locked_until = NULL WHERE user_id = ?", userId);
    }

    @Override
    public boolean isLocked(String userId) {
        var results = jdbc.query(
                "SELECT 1 FROM users WHERE user_id = ? AND locked_until > now()",
                (rs, rowNum) -> true, userId);
        return !results.isEmpty();
    }

    @Override
    public void revokeTokensBefore(String userId, Instant timestamp) {
        jdbc.update("UPDATE users SET token_revoked_before = ? WHERE user_id = ?",
                java.sql.Timestamp.from(timestamp), userId);
    }

    @Override
    public long count() {
        Long n = jdbc.queryForObject("SELECT COUNT(*) FROM users", Long.class);
        return n == null ? 0L : n;
    }

    private UserInfo mapUser(java.sql.ResultSet rs) throws java.sql.SQLException {
        java.sql.Timestamp ts = rs.getTimestamp("created_at");
        java.time.Instant createdAt = ts != null ? ts.toInstant() : null;
        java.sql.Timestamp lockedTs = rs.getTimestamp("locked_until");
        java.time.Instant lockedUntil = lockedTs != null ? lockedTs.toInstant() : null;
        java.sql.Timestamp revokedTs = rs.getTimestamp("token_revoked_before");
        java.time.Instant tokenRevokedBefore = revokedTs != null ? revokedTs.toInstant() : null;
        return new UserInfo(
                rs.getString("user_id"), rs.getString("provider"),
                rs.getString("email"), rs.getString("display_name"),
                createdAt, rs.getInt("failed_login_attempts"),
                lockedUntil, tokenRevokedBefore);
    }
}
