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.time.LocalDate;
import java.util.List;
import java.util.Map;

/**
 * Router CRUD untuk resource tugas: membaca method dan path dari setiap
 * permintaan, memvalidasi payload lewat ValidasiTugas, lalu menghasilkan
 * respons JSON. Seluruh kegagalan diterjemahkan di satu titik menjadi
 * respons berstatus.
 */
public class RouterTugas implements HttpHandler {

    private final TugasRepository repo;

    public RouterTugas(TugasRepository repo) {
        this.repo = repo;
    }

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        try {
            tangani(exchange);
        } catch (ApiException e) {
            kirim(exchange, e.status(), JsonMini.error(e.getMessage()));
        } catch (IllegalArgumentException e) {
            // Validasi pada lapisan domain (record, enum, penyimpanan) diterjemahkan jadi 400.
            kirim(exchange, 400, JsonMini.error(e.getMessage()));
        } catch (RuntimeException e) {
            kirim(exchange, 500, JsonMini.error("kesalahan internal: " + e.getMessage()));
        } finally {
            exchange.close();
        }
    }

    /** Membaca method dan path, lalu mengarahkan ke handler yang tepat. */
    private void tangani(HttpExchange ex) throws IOException {
        String metode = ex.getRequestMethod();
        String path = ex.getRequestURI().getPath();

        if (!path.equals("/api/tugas") && !path.startsWith("/api/tugas/")) {
            throw new ApiException(404, "endpoint tidak dikenal: " + path);
        }
        String sisa = path.substring("/api/tugas".length());

        if (sisa.isEmpty() || sisa.equals("/")) {
            switch (metode) {
                case "GET" -> daftar(ex);
                case "POST" -> tambah(ex);
                default -> lempar405(metode, path);
            }
        } else {
            int id = bacaId(sisa);
            switch (metode) {
                case "GET" -> detail(ex, id);
                case "PUT" -> ubah(ex, id);
                case "DELETE" -> hapus(ex, id);
                default -> lempar405(metode, path);
            }
        }
    }

    private void daftar(HttpExchange ex) throws IOException {
        kirim(ex, 200, JsonMini.daftar(repo.semua()));
    }

    /** POST: status awal selalu TODO; server yang menetapkannya, bukan klien. */
    private void tambah(HttpExchange ex) throws IOException {
        Map<String, Object> payload = bacaPayload(ex);
        List<String> galat = ValidasiTugas.galatBuat(payload);
        if (!galat.isEmpty()) {
            throw new ValidasiGagalException(String.join("; ", galat));
        }
        Tugas baru = new Tugas(0, teks(payload, "judul"), teks(payload, "pemilik"),
                StatusTugas.TODO, LocalDate.parse(teks(payload, "tenggat")));
        Tugas tersimpan = repo.tambah(baru);
        kirim(ex, 201, JsonMini.tugas(tersimpan));
    }

    private void detail(HttpExchange ex, int id) throws IOException {
        Tugas tugas = repo.cari(id).orElseThrow(() -> new TugasTidakDitemukanException(id));
        kirim(ex, 200, JsonMini.tugas(tugas));
    }

    /** PUT: penggantian utuh, kelima field dikirim kembali oleh klien. */
    private void ubah(HttpExchange ex, int id) throws IOException {
        repo.cari(id).orElseThrow(() -> new TugasTidakDitemukanException(id));
        Map<String, Object> payload = bacaPayload(ex);
        List<String> galat = ValidasiTugas.galatUbah(payload);
        if (!galat.isEmpty()) {
            throw new ValidasiGagalException(String.join("; ", galat));
        }
        Tugas pengganti = new Tugas(id, teks(payload, "judul"), teks(payload, "pemilik"),
                StatusTugas.dariTeks(teks(payload, "status")),
                LocalDate.parse(teks(payload, "tenggat")));
        Tugas tersimpan = repo.simpan(pengganti)
                .orElseThrow(() -> new TugasTidakDitemukanException(id));
        kirim(ex, 200, JsonMini.tugas(tersimpan));
    }

    private void hapus(HttpExchange ex, int id) throws IOException {
        if (!repo.hapus(id)) {
            throw new TugasTidakDitemukanException(id);
        }
        kirim(ex, 200, "{\"pesan\":\"tugas " + id + " dihapus\"}");
    }

    /** Membaca segmen path setelah /api/tugas/ sebagai id. */
    private static int bacaId(String sisa) {
        String teks = sisa.substring(1);
        if (teks.contains("/")) {
            throw new ApiException(404, "endpoint tidak dikenal: /api/tugas" + sisa);
        }
        long id;
        try {
            id = Long.parseLong(teks);
        } catch (NumberFormatException e) {
            throw new ValidasiGagalException("id harus angka, diterima: '" + teks + "'");
        }
        if (id < 1 || id > Integer.MAX_VALUE) {
            throw new ValidasiGagalException("id di luar rentang: " + id);
        }
        return (int) id;
    }

    /** Membaca seluruh body permintaan sebagai objek JSON datar. */
    private static Map<String, Object> bacaPayload(HttpExchange ex) throws IOException {
        String mentah = new String(ex.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
        if (mentah.isBlank()) {
            throw new ValidasiGagalException("body permintaan tidak boleh kosong");
        }
        try {
            return JsonMini.parseObjek(mentah);
        } catch (IllegalArgumentException e) {
            throw new ValidasiGagalException("JSON tidak valid: " + e.getMessage());
        }
    }

    /** Nilai field teks yang sudah divalidasi; tidak akan melempar pada pemanggilan sah. */
    private static String teks(Map<String, Object> payload, String kunci) {
        return (String) payload.get(kunci);
    }

    private static void lempar405(String metode, String path) {
        throw new ApiException(405, metode + " tidak didukung di " + path
                + "; gunakan GET, POST, PUT, atau DELETE");
    }

    /** Satu-satunya pintu menulis respons: header JSON, status, lalu body UTF-8. */
    private static void kirim(HttpExchange ex, int status, String json) throws IOException {
        byte[] isi = json.getBytes(StandardCharsets.UTF_8);
        ex.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
        ex.sendResponseHeaders(status, isi.length);
        try (OutputStream os = ex.getResponseBody()) {
            os.write(isi);
        }
    }
}
