import os
import shutil
import torch
import numpy as np
import pandas as pd

from tqdm import tqdm
from suno_utils.utils.text import write_jsonl, read_jsonl

if __name__ == "__main__":
    # --------------------------------------------------------------------------
    # Constants
    VAE_MEMMAP_SIZE = 750
    SEMANTIC_MEMMAP_SIZE = 750
    VAE_DIM = 128
    CHUNK_SIZE = 100

    # set dataset type
    DSET_TYPE = "val"

    # load pkl file
    DATA_PKL_PATH = (
        "/home/tony/Data/Preference/up_v1/interesting_clips_up_v1_20241118_full.pkl"
    )
    NPZ_DIR = "/app/suno/data/dpo/diff_v1"
    OUT_DATA_DIR = "/app/suno/data/diffusion_ft/interesting_clips_up_v1_20241118_full"

    # --------------------------------------------------------------------------

    # create output dir
    if os.path.exists(OUT_DATA_DIR):
        # delete it
        shutil.rmtree(OUT_DATA_DIR)
    os.makedirs(OUT_DATA_DIR, exist_ok=True)

    df = pd.read_pickle(DATA_PKL_PATH)
    print(f"df shape: {df.shape}")

    # create memmap files
    out_mm_vae_filepath = os.path.join(OUT_DATA_DIR, f"data_vae_{DSET_TYPE}.bin")
    out_metas_filepath = os.path.join(OUT_DATA_DIR, f"metas_{DSET_TYPE}.jsonl")
    out_mm_semantic_filepath = os.path.join(
        OUT_DATA_DIR, f"data_semantic_{DSET_TYPE}.bin"
    )

    # initial write
    out_mm_semantic = np.memmap(
        out_mm_semantic_filepath,
        dtype=np.uint16,
        mode="w+",
        shape=(1),
    )
    out_mm_vae = np.memmap(
        out_mm_vae_filepath,
        dtype=np.float16,
        mode="w+",
        shape=(1),
    )

    n_offs_s = 0
    n_offs_v = 0

    # create indices for the df
    # we will count by 2, so take all pairs of rows starting at index 0
    df_indices = list(range(0, len(df), 2))

    # now chunk the df indices
    df_indices_chunks = [
        df_indices[i : i + CHUNK_SIZE] for i in range(0, len(df_indices), CHUNK_SIZE)
    ]

    print(f"total chunks: {len(df_indices_chunks)}")

    # two consecutive rows form the positive and negative pair
    for chunk_idx, df_indices in enumerate(tqdm(df_indices_chunks)):
        arr_s_list = []
        arr_v_list = []
        new_metas = []

        for i in df_indices:
            positive_row = df.iloc[i]
            negative_row = df.iloc[i + 1]

            # load npz files
            positive_npz_path = os.path.join(NPZ_DIR, f"{positive_row['id_x']}.npz")
            negative_npz_path = os.path.join(NPZ_DIR, f"{negative_row['id_x']}.npz")

            for id_x in [positive_row["id_x"], negative_row["id_x"]]:
                # get semantic codes
                coarse_npz_path = os.path.join(NPZ_DIR, f"{id_x}.npz")
                coarse_npz = np.load(coarse_npz_path)
                codes = coarse_npz["v3.0_raw"]
                semantic_codes = codes[:, 0].astype(np.uint16)
                arr_s = semantic_codes[:SEMANTIC_MEMMAP_SIZE]

                # get vae latents
                vae_npz_path = os.path.join(NPZ_DIR, f"{id_x}_vae.npz")
                vae_npz = np.load(vae_npz_path)
                vae_latents = vae_npz["vae_latents"]
                arr_v = vae_latents[:VAE_MEMMAP_SIZE, :].astype(np.float16)

                # check for nan in arr_v or arr_s
                if not np.all(np.isfinite(arr_v)) or not np.all(np.isfinite(arr_s)):
                    continue

                # create a new meta
                meta = {
                    "text": positive_row["metadata"]["prompt"],
                    "tags": positive_row["metadata"]["tags"],
                    "n_vae_tokens": VAE_MEMMAP_SIZE,
                    "start_s": 0.0,
                    "end_s": 30.0,
                }

                try:
                    if arr_s.size < SEMANTIC_MEMMAP_SIZE:
                        continue
                    if arr_v.size < VAE_MEMMAP_SIZE * VAE_DIM:
                        continue
                except Exception as e:
                    print(f"error loading {meta['id']}: {e}")
                    continue

                arr_s_list.append(arr_s)
                arr_v_list.append(arr_v)
                new_metas.append(meta)

        print(len(arr_s_list), len(arr_v_list), len(new_metas))
        assert len(arr_s_list) == len(arr_v_list) == len(new_metas)

        to_write_len_s = SEMANTIC_MEMMAP_SIZE * len(arr_s_list)
        to_write_len_v = VAE_MEMMAP_SIZE * VAE_DIM * len(arr_v_list)

        out_mm_semantic = np.memmap(
            out_mm_semantic_filepath,
            dtype=np.uint16,
            mode="r+",
            shape=(n_offs_s + to_write_len_s,),
        )

        out_mm_vae = np.memmap(
            out_mm_vae_filepath,
            dtype=np.float16,
            mode="r+",
            shape=(n_offs_v + to_write_len_v,),
        )

        # write to memmap (has to happen sequentially)
        for new_meta, arr_s, arr_v in zip(new_metas, arr_s_list, arr_v_list):
            out_mm_semantic[n_offs_s : n_offs_s + arr_s.size] = arr_s.reshape(
                -1,
            )
            out_mm_vae[n_offs_v : n_offs_v + arr_v.size] = arr_v.reshape(
                -1,
            )
            n_offs_s += arr_s.size
            n_offs_v += arr_v.size

        # write it once
        out_mm_semantic.flush()
        out_mm_vae.flush()
        del out_mm_semantic, out_mm_vae

        write_jsonl(new_metas, out_metas_filepath, do_append=True)
