import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

/**
 * Companion for "Java Interview Questions for 5 Years Experience". Eight production scenarios,
 * each one a bug you are likely to be asked to explain.   Run: java ProductionScenarios.java  (JDK 21+)
 */
public class ProductionScenarios {
    public static void main(String[] args) throws Exception {
        lostUpdate();
        deadlock();
        mutableKey();
        threadLocalLeak();
        parallelStreamSharedList();
        equalityTraps();
        completableFutureTimeout();
        finallyAndSuppressed();
    }

    // 1. "Our counter is lower than the number of requests" ------------------------------------
    static int plain = 0;
    static void lostUpdate() throws Exception {
        System.out.println("[1] lost update: 4 threads x 500000 increments");
        AtomicInteger atomic = new AtomicInteger();
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        Runnable work = () -> {
            for (int i = 0; i < 500_000; i++) { plain++; atomic.incrementAndGet(); map.merge("hits", 1, Integer::sum); }
        };
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 4; i++) { Thread t = new Thread(work); ts.add(t); t.start(); }
        for (Thread t : ts) t.join();
        System.out.println("    expected              = 2000000");
        System.out.println("    plain int++ lost some = " + (plain < 2_000_000));
        System.out.println("    AtomicInteger         = " + atomic.get());
        System.out.println("    ConcurrentHashMap.merge = " + map.get("hits"));
    }

    // 2. Deadlock: detect it, then fix it with a global lock order -------------------------------------
    static void deadlock() throws Exception {
        System.out.println("[2] deadlock");
        ReentrantLock a = new ReentrantLock(), b = new ReentrantLock();
        CountDownLatch bothHoldOne = new CountDownLatch(2);
        Thread t1 = new Thread(() -> grab(a, b, bothHoldOne), "transfer-A-to-B");
        Thread t2 = new Thread(() -> grab(b, a, bothHoldOne), "transfer-B-to-A");
        t1.start(); t2.start();
        Thread.sleep(500);
        long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
        System.out.println("    ThreadMXBean.findDeadlockedThreads() -> " + (ids == null ? 0 : ids.length) + " threads");
        t1.interrupt(); t2.interrupt(); t1.join(); t2.join();

        // fix: every thread takes the locks in the same order
        Thread f1 = new Thread(() -> ordered(a, b));
        Thread f2 = new Thread(() -> ordered(a, b));
        f1.start(); f2.start(); f1.join(2000); f2.join(2000);
        System.out.println("    with one global lock order, both threads finished: " + (!f1.isAlive() && !f2.isAlive()));
    }
    static void grab(ReentrantLock first, ReentrantLock second, CountDownLatch latch) {
        try {
            first.lockInterruptibly();
            latch.countDown(); latch.await();
            second.lockInterruptibly();
            second.unlock();
        } catch (InterruptedException ignored) {
        } finally { if (first.isHeldByCurrentThread()) first.unlock(); }
    }
    static void ordered(ReentrantLock a, ReentrantLock b) {
        a.lock(); try { b.lock(); try { /* transfer */ } finally { b.unlock(); } } finally { a.unlock(); }
    }

    // 3. "The entry is in the map but get() returns null" -----------------------------------------------
    static class Key {
        String id;
        Key(String id) { this.id = id; }
        @Override public boolean equals(Object o) { return o instanceof Key k && k.id.equals(id); }
        @Override public int hashCode() { return id.hashCode(); }
    }
    static void mutableKey() {
        System.out.println("[3] mutable HashMap key");
        Map<Key, String> m = new HashMap<>();
        Key k = new Key("user-1");
        m.put(k, "profile");
        k.id = "user-2";
        System.out.println("    size after mutating the key = " + m.size());
        System.out.println("    get(sameKeyObject)          = " + m.get(k));
        System.out.println("    get(new Key(\"user-1\"))      = " + m.get(new Key("user-1")));
    }

    // 4. ThreadLocal on a pooled thread leaks data into the next request ------------------------------------
    static final ThreadLocal<String> CURRENT_USER = new ThreadLocal<>();
    static void threadLocalLeak() throws Exception {
        System.out.println("[4] ThreadLocal + thread pool");
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool.submit(() -> CURRENT_USER.set("user-A")).get();
        System.out.println("    next request on the same thread sees: " + pool.submit(CURRENT_USER::get).get());
        pool.submit(() -> { try { CURRENT_USER.set("user-B"); } finally { CURRENT_USER.remove(); } }).get();
        System.out.println("    after remove() in finally, it sees:     " + pool.submit(CURRENT_USER::get).get());
        pool.shutdown();
    }

    // 5. parallel stream writing into an ArrayList --------------------------------------------------------------------
    static void parallelStreamSharedList() {
        System.out.println("[5] parallel stream + shared ArrayList");
        List<Integer> shared = new ArrayList<>();
        String outcome;
        try {
            IntStream.range(0, 100_000).parallel().forEach(shared::add);
            outcome = shared.size() == 100_000 ? "got all 100000 (lucky run)" : "lost elements, size < 100000";
        } catch (RuntimeException e) {
            outcome = "threw " + e.getClass().getSimpleName();
        }
        System.out.println("    forEach(shared::add) -> " + (outcome.startsWith("got all") ? "unreliable" : "broken") + " (" + outcome + ")");
        List<Integer> safe = IntStream.range(0, 100_000).parallel().boxed().toList();
        System.out.println("    collect via toList()  -> size " + safe.size());
    }

    // 6. equality traps ----------------------------------------------------------------------------------------------------
    static void equalityTraps() {
        System.out.println("[6] equality traps");
        Integer a = 127, b = 127, c = 128, d = 128;
        System.out.println("    Integer 127 == 127 : " + (a == b));
        System.out.println("    Integer 128 == 128 : " + (c == d));
        BigDecimal x = new BigDecimal("2.0"), y = new BigDecimal("2.00");
        System.out.println("    BigDecimal 2.0 equals 2.00    : " + x.equals(y));
        System.out.println("    BigDecimal 2.0 compareTo 2.00 : " + x.compareTo(y));
    }

    // 7. CompletableFuture: timeouts and exceptions ----------------------------------------------------------------------
    static void completableFutureTimeout() throws Exception {
        System.out.println("[7] CompletableFuture");
        CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
            try { Thread.sleep(2_000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            return "late";
        });
        String r = slow.orTimeout(200, TimeUnit.MILLISECONDS)
                .exceptionally(ex -> "fallback after " + ex.getClass().getSimpleName()).get();
        System.out.println("    " + r);
        CompletableFuture<Integer> failing = CompletableFuture.supplyAsync(() -> { throw new IllegalStateException("boom"); });
        try { failing.thenApply(v -> v + 1).get(); }
        catch (ExecutionException e) { System.out.println("    get() wraps the cause: " + e.getCause()); }
    }

    // 8. finally and suppressed exceptions -----------------------------------------------------------------------------------
    static class Res implements AutoCloseable {
        public void close() { throw new IllegalStateException("close failed"); }
    }
    @SuppressWarnings("finally")
    static int finallyWins() { try { return 1; } finally { return 2; } }
    static void finallyAndSuppressed() {
        System.out.println("[8] finally and try-with-resources");
        System.out.println("    return in try, return in finally -> " + finallyWins());
        try (Res r = new Res()) {
            throw new RuntimeException("work failed");
        } catch (RuntimeException e) {
            System.out.println("    primary   : " + e.getMessage());
            System.out.println("    suppressed: " + e.getSuppressed()[0].getMessage());
        }
    }
}
