import os
import uuid
from pathlib import Path
from fastapi import UploadFile, HTTPException

from app.core.config import UPLOAD_DIR, ALLOWED_EXTENSIONS, MAX_FILE_SIZE_BYTES

# Ukuran potongan baca per iterasi (1 MB)
_CHUNK_SIZE = 1024 * 1024


def validate_audio_file(file: UploadFile) -> None:
    """
    Validasi ekstensi file sebelum disimpan.

    FIX #1: Tambah pengecekan filename kosong/None agar tidak crash
            saat Path(None).suffix dipanggil.
    FIX #2: Pindahkan validasi ekstensi ke sini agar bisa dipanggil
            lebih awal sebelum membaca konten apapun ke memori.
    """
    if not file.filename:
        raise HTTPException(
            status_code=400,
            detail="Nama file tidak boleh kosong."
        )

    ext = Path(file.filename).suffix.lower()

    if not ext:
        raise HTTPException(
            status_code=400,
            detail="File tidak memiliki ekstensi. "
                   f"Gunakan: {', '.join(sorted(ALLOWED_EXTENSIONS))}"
        )

    if ext not in ALLOWED_EXTENSIONS:
        raise HTTPException(
            status_code=400,
            detail=(
                f"Format file tidak didukung: '{ext}'. "
                f"Gunakan: {', '.join(sorted(ALLOWED_EXTENSIONS))}"
            )
        )


async def save_upload_file(file: UploadFile) -> str:
    """
    Simpan file upload ke direktori UPLOAD_DIR dengan nama unik.
    Kembalikan path absolut file yang tersimpan.

    FIX #3: Baca file per chunk (streaming) sambil menghitung ukuran,
            sehingga file 100 MB tidak langsung dimuat penuh ke RAM.
            Jika ukuran melebihi batas, file parsial langsung dihapus
            dan HTTPException 413 dilempar — konten tidak pernah
            benar-benar tersimpan penuh.

    FIX #4: Tangani exception I/O saat menulis ke disk agar tidak
            meninggalkan file parsial yang tidak terhapus.
    """
    # Pastikan ekstensi valid sebelum menyentuh disk
    validate_audio_file(file)

    ext         = Path(file.filename).suffix.lower()
    unique_name = f"{uuid.uuid4().hex}{ext}"
    file_path   = UPLOAD_DIR / unique_name

    total_bytes = 0

    try:
        with open(file_path, "wb") as out:
            while True:
                chunk = await file.read(_CHUNK_SIZE)
                if not chunk:
                    break

                total_bytes += len(chunk)

                # FIX #3: cek ukuran per chunk, tolak sebelum tulis penuh
                if total_bytes > MAX_FILE_SIZE_BYTES:
                    raise HTTPException(
                        status_code=413,
                        detail=(
                            f"Ukuran file melebihi batas maksimum "
                            f"{MAX_FILE_SIZE_BYTES // (1024 * 1024)} MB."
                        )
                    )

                out.write(chunk)

    except HTTPException:
        # FIX #4: hapus file parsial sebelum meneruskan exception
        delete_file(str(file_path))
        raise

    except OSError as exc:
        # FIX #4: tangani error disk (full, permission, dll.)
        delete_file(str(file_path))
        raise HTTPException(
            status_code=500,
            detail=f"Gagal menyimpan file ke disk: {exc}"
        ) from exc

    # FIX #5: tolak file yang benar-benar kosong (0 byte)
    if total_bytes == 0:
        delete_file(str(file_path))
        raise HTTPException(
            status_code=400,
            detail="File tidak boleh kosong."
        )

    return str(file_path)


def delete_file(file_path: str | None) -> None:
    """
    Hapus file jika ada.
    Dipakai untuk cleanup setelah error atau penghapusan task.

    FIX #6: Terima None secara eksplisit di type hint agar type checker
            tidak komplain. Tidak melempar exception apapun — selalu
            silent agar caller tidak perlu try/except.
    """
    if not file_path:
        return
    try:
        path = Path(file_path)
        if path.exists() and path.is_file():   # FIX #6: pastikan itu file, bukan direktori
            path.unlink()
    except OSError:
        pass                                    # silent — dipanggil saat cleanup, jangan crash