import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Gerbang keamanan di depan router: dekorator HttpHandler yang memeriksa
 * header Authorization skema Basic sebelum permintaan diteruskan. Struktur
 * ini adalah miniatur filter chain Spring Security: satu pembungkus di luar
 * seluruh aplikasi, kode aplikasi tidak menyentuh satu baris pun.
 *
 * Autentikasi: kredensial "nama:sandi" dienkode base64 pada header; nama
 * dicari di daftar pengguna terdaftar dan sandi dibandingkan konstan-waktu.
 * Otorisasi: DELETE hanya untuk peran ADMIN, ditolak 403 bila peran kurang.
 * Seluruh penolakan autentikasi dijawab 401 plus header WWW-Authenticate
 * yang diwajibkan RFC 7235 (dan RFC 7617 untuk parameter charset).
 */
public class FilterTokenWeb implements HttpHandler {

    private final HttpHandler target;
    private final Map<String, PenggunaWeb> pengguna = new LinkedHashMap<>();

    public FilterTokenWeb(HttpHandler target, PenggunaWeb... penggunaTerdaftar) {
        this.target = target;
        for (PenggunaWeb p : penggunaTerdaftar) {
            pengguna.put(p.nama(), p);
        }
    }

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        try {
            PenggunaWeb terverifikasi = autentikasi(exchange);
            otorisasi(exchange, terverifikasi);
        } catch (ApiException e) {
            tolak(exchange, e.status(), e.getMessage());
            return;   // permintaan ditolak: router tidak pernah dipanggil
        }
        target.handle(exchange);   // router punya penanganan galat dan penutupan exchange sendiri
    }

    /**
     * Memeriksa kredensial Basic pada header Authorization.
     * Melempar ApiException 401 bila kredensial tidak sah.
     */
    private PenggunaWeb autentikasi(HttpExchange ex) {
        String header = ex.getRequestHeaders().getFirst("Authorization");
        if (header == null || !header.startsWith("Basic ")) {
            throw new ApiException(401,
                    "header Authorization wajib berformat 'Basic <kredensial>'");
        }
        byte[] oktet;
        try {
            oktet = Base64.getDecoder().decode(header.substring("Basic ".length()).strip());
        } catch (IllegalArgumentException e) {
            throw new ApiException(401, "kredensial Basic bukan base64 yang sah");
        }
        String teks = new String(oktet, StandardCharsets.UTF_8);
        // Pemisah pertama: sandi boleh memuat karakter ':' sedangkan nama tidak.
        int posisi = teks.indexOf(':');
        if (posisi < 1) {
            throw new ApiException(401, "kredensial harus berformat 'nama:sandi'");
        }
        String nama = teks.substring(0, posisi);
        String sandi = teks.substring(posisi + 1);

        PenggunaWeb terdaftar = pengguna.get(nama);
        if (terdaftar == null || !terdaftar.cocokSandi(sandi)) {
            // Pesan seragam untuk nama tak dikenal dan sandi salah,
            // supaya tidak membocorkan nama mana yang terdaftar.
            throw new ApiException(401, "nama atau sandi salah");
        }
        return terdaftar;
    }

    /**
     * Memeriksa izin per permintaan: menghapus tugas hanya untuk ADMIN.
     * Melempar ApiException 403 bila peran tidak mencukupi.
     */
    private void otorisasi(HttpExchange ex, PenggunaWeb terverifikasi) {
        if ("DELETE".equals(ex.getRequestMethod())
                && terverifikasi.peran() != PenggunaWeb.Peran.ADMIN) {
            throw new ApiException(403, "menghapus tugas memerlukan peran ADMIN"
                    + " (terautentikasi sebagai " + terverifikasi.nama() + ")");
        }
    }

    /** Menulis respons penolakan: body JSON, header WWW-Authenticate untuk 401. */
    private static void tolak(HttpExchange ex, int status, String pesan) throws IOException {
        ex.getRequestBody().readAllBytes();   // buang body belum terbaca agar koneksi sehat
        byte[] isi = JsonWeb.galat(status, pesan).getBytes(StandardCharsets.UTF_8);
        ex.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
        if (status == 401) {
            ex.getResponseHeaders().set("WWW-Authenticate",
                    "Basic realm=\"tugas-web\", charset=\"UTF-8\"");
        }
        ex.sendResponseHeaders(status, isi.length);
        try (OutputStream os = ex.getResponseBody()) {
            os.write(isi);
        } finally {
            ex.close();
        }
    }
}
