"""Lapisan akses data untuk aplikasi inventaris.

Seluruh function operasi data menerima objek koneksi sebagai
parameter pertama. Aplikasi memakai satu koneksi selama program
berjalan, sedangkan test memakai database :memory: miliknya sendiri.
"""

import sqlite3
from pathlib import Path

NAMA_DATABASE = "inventaris.db"
BERKAS_SKEMA = Path(__file__).with_name("schema.sql")
BATAS_STOK_MENIPIS = 5
JUMLAH_TOP_PRODUK = 5


def koneksi(db_path: str = NAMA_DATABASE) -> sqlite3.Connection:
    """Buka koneksi baru ke database inventaris."""
    conn = sqlite3.connect(db_path)
    conn.execute("PRAGMA foreign_keys = ON")
    conn.row_factory = sqlite3.Row
    return conn


def inisialisasi(db_path: str = NAMA_DATABASE) -> None:
    """Terapkan schema.sql ke database. Aman dijalankan berulang."""
    conn = koneksi(db_path)
    try:
        conn.executescript(BERKAS_SKEMA.read_text(encoding="utf-8"))
        conn.commit()
    finally:
        conn.close()


def validasi_produk(nama: str, kategori: str, harga: int, stok: int) -> None:
    """Periksa masukan produk; angkat ValueError bila tidak sah."""
    if not nama.strip():
        raise ValueError("Nama produk tidak boleh kosong.")
    if not kategori.strip():
        raise ValueError("Kategori tidak boleh kosong.")
    if isinstance(harga, bool) or not isinstance(harga, int):
        raise ValueError("Harga harus berupa bilangan bulat.")
    if harga < 0:
        raise ValueError("Harga tidak boleh negatif.")
    if isinstance(stok, bool) or not isinstance(stok, int):
        raise ValueError("Stok harus berupa bilangan bulat.")
    if stok < 0:
        raise ValueError("Stok tidak boleh negatif.")


def tambah_produk(conn: sqlite3.Connection, nama: str, kategori: str,
                  harga: int, stok: int) -> int:
    """Simpan produk baru, kembalikan id-nya."""
    sql = "INSERT INTO produk (nama, kategori, harga, stok) VALUES (?, ?, ?, ?)"
    kursor = conn.execute(sql, (nama, kategori, harga, stok))
    conn.commit()
    return int(kursor.lastrowid)


def lihat_produk(conn: sqlite3.Connection) -> list[sqlite3.Row]:
    """Ambil seluruh produk, urut berdasarkan nama."""
    return conn.execute("SELECT * FROM produk ORDER BY nama").fetchall()


def ambil_produk(conn: sqlite3.Connection,
                 produk_id: int) -> sqlite3.Row | None:
    """Ambil satu produk berdasarkan id; None bila tidak ada."""
    kursor = conn.execute("SELECT * FROM produk WHERE id = ?", (produk_id,))
    return kursor.fetchone()


def perbarui_harga(conn: sqlite3.Connection, produk_id: int,
                   harga_baru: int) -> bool:
    """Set harga produk menjadi nilai baru. False bila id tidak ada."""
    kursor = conn.execute(
        "UPDATE produk SET harga = ? WHERE id = ?", (harga_baru, produk_id)
    )
    conn.commit()
    return kursor.rowcount > 0


def sesuaikan_stok(conn: sqlite3.Connection, produk_id: int, perubahan: int,
                   keterangan: str) -> None:
    """Ubah stok sebesar perubahan (+/-) dan catat ke riwayat secara atomik."""
    with conn:  # commit bila sukses, rollback bila ada galat
        kursor = conn.execute(
            "UPDATE produk SET stok = stok + ? WHERE id = ?",
            (perubahan, produk_id),
        )
        if kursor.rowcount == 0:
            raise ValueError(f"Produk dengan id {produk_id} tidak ditemukan.")
        conn.execute(
            "INSERT INTO riwayat (produk_id, perubahan, keterangan)"
            " VALUES (?, ?, ?)",
            (produk_id, perubahan, keterangan),
        )


def lihat_riwayat(conn: sqlite3.Connection,
                  produk_id: int) -> list[sqlite3.Row]:
    """Ambil riwayat penyesuaian stok sebuah produk, terbaru lebih dulu."""
    return conn.execute(
        "SELECT * FROM riwayat WHERE produk_id = ? ORDER BY id DESC",
        (produk_id,),
    ).fetchall()


def hapus_produk(conn: sqlite3.Connection, produk_id: int) -> bool:
    """Hapus produk beserta riwayatnya. False bila id tidak ada."""
    kursor = conn.execute("DELETE FROM produk WHERE id = ?", (produk_id,))
    conn.commit()
    return kursor.rowcount > 0


def escape_like(keyword: str) -> str:
    """Amankan karakter wildcard LIKE (persen dan garis bawah)."""
    return (
        keyword.replace("\\", "\\\\")
        .replace("%", "\\%")
        .replace("_", "\\_")
    )


def cari_produk(conn: sqlite3.Connection, keyword: str) -> list[sqlite3.Row]:
    """Cari produk yang namanya mengandung keyword (LIKE yang di-escape)."""
    pola = f"%{escape_like(keyword)}%"
    sql = "SELECT * FROM produk WHERE nama LIKE ? ESCAPE '\\' ORDER BY nama"
    return conn.execute(sql, (pola,)).fetchall()


def filter_kategori(conn: sqlite3.Connection,
                    kategori: str) -> list[sqlite3.Row]:
    """Ambil seluruh produk pada satu kategori."""
    sql = "SELECT * FROM produk WHERE kategori = ? ORDER BY nama"
    return conn.execute(sql, (kategori,)).fetchall()


def laporan_stok_menipis(conn: sqlite3.Connection,
                         batas: int = BATAS_STOK_MENIPIS) -> list[sqlite3.Row]:
    """Ambil produk dengan stok di bawah batas, stok terkecil lebih dulu."""
    sql = "SELECT * FROM produk WHERE stok < ? ORDER BY stok, nama"
    return conn.execute(sql, (batas,)).fetchall()


def nilai_inventaris_per_kategori(
    conn: sqlite3.Connection,
) -> list[sqlite3.Row]:
    """Hitung jumlah produk dan total nilai stok per kategori."""
    sql = """
        SELECT kategori,
               COUNT(*) AS jumlah_produk,
               SUM(harga * stok) AS nilai_total
        FROM produk
        GROUP BY kategori
        ORDER BY nilai_total DESC
    """
    return conn.execute(sql).fetchall()


def top_produk(conn: sqlite3.Connection,
               jumlah: int = JUMLAH_TOP_PRODUK) -> list[sqlite3.Row]:
    """Ambil produk dengan nilai stok terbesar."""
    sql = """
        SELECT id, nama, kategori, harga, stok,
               harga * stok AS nilai
        FROM produk
        ORDER BY nilai DESC, nama
        LIMIT ?
    """
    return conn.execute(sql, (jumlah,)).fetchall()
