import os
import requests
import time
import json
import numpy as np
import torch
import soundfile as sf
import librosa
from pydub import AudioSegment

import kaldi_native_fbank as knf
import onnxruntime as ort

from sbert_punc_case_ru import SbertPuncCase


# --- сюда вставляем класс OnnxModel и все функции create_fbank и compute_features ---

def create_fbank():
    opts = knf.FbankOptions()
    opts.frame_opts.dither = 0
    opts.frame_opts.remove_dc_offset = False
    opts.frame_opts.preemph_coeff = 0
    opts.frame_opts.window_type = "hann"
    opts.frame_opts.round_to_power_of_two = False
    opts.mel_opts.low_freq = 0
    opts.mel_opts.high_freq = 8000
    opts.mel_opts.num_bins = 64
    fbank = knf.OnlineFbank(opts)
    return fbank

def compute_features(audio, fbank):
    assert len(audio.shape) == 1, audio.shape
    fbank.accept_waveform(16000, audio)
    ans = []
    processed = 0
    while processed < fbank.num_frames_ready:
        ans.append(np.array(fbank.get_frame(processed)))
        processed += 1
    ans = np.stack(ans)
    return ans

class OnnxModel:
    def __init__(self, encoder: str, decoder: str, joiner: str):
        self.init_encoder(encoder)
        self.init_decoder(decoder)
        self.init_joiner(joiner)

    def init_encoder(self, encoder):
        session_opts = ort.SessionOptions()
        session_opts.inter_op_num_threads = 1
        session_opts.intra_op_num_threads = 1
        self.encoder = ort.InferenceSession(
            encoder,
            sess_options=session_opts,
            providers=["CPUExecutionProvider"],
        )
        meta = self.encoder.get_modelmeta().custom_metadata_map
        self.normalize_type = meta.get("normalize_type", "")
        self.pred_rnn_layers = int(meta.get("pred_rnn_layers", "1"))
        self.pred_hidden = int(meta.get("pred_hidden", "320"))

    def init_decoder(self, decoder):
        session_opts = ort.SessionOptions()
        session_opts.inter_op_num_threads = 1
        session_opts.intra_op_num_threads = 1
        self.decoder = ort.InferenceSession(
            decoder,
            sess_options=session_opts,
            providers=["CPUExecutionProvider"],
        )

    def init_joiner(self, joiner):
        session_opts = ort.SessionOptions()
        session_opts.inter_op_num_threads = 1
        session_opts.intra_op_num_threads = 1
        self.joiner = ort.InferenceSession(
            joiner,
            sess_options=session_opts,
            providers=["CPUExecutionProvider"],
        )

    def get_decoder_state(self):
        batch_size = 1
        state0 = torch.zeros(self.pred_rnn_layers, batch_size, self.pred_hidden).numpy()
        state1 = torch.zeros(self.pred_rnn_layers, batch_size, self.pred_hidden).numpy()
        return state0, state1

    def run_encoder(self, x: np.ndarray):
        x = torch.from_numpy(x)
        x = x.t().unsqueeze(0)  # Shape: [1, C, T]
        x_lens = torch.tensor([x.shape[-1]], dtype=torch.int64)

        (encoder_out, out_len) = self.encoder.run(
            [
                self.encoder.get_outputs()[0].name,
                self.encoder.get_outputs()[1].name,
            ],
            {
                self.encoder.get_inputs()[0].name: x.numpy(),
                self.encoder.get_inputs()[1].name: x_lens.numpy(),
            },
        )
        return encoder_out

    def run_decoder(self, token: int, state0: np.ndarray, state1: np.ndarray):
        target = torch.tensor([[token]], dtype=torch.int32).numpy()
        target_len = torch.tensor([1], dtype=torch.int32).numpy()

        (decoder_out, decoder_out_length, state0_next, state1_next,) = self.decoder.run(
            [
                self.decoder.get_outputs()[0].name,
                self.decoder.get_outputs()[1].name,
                self.decoder.get_outputs()[2].name,
                self.decoder.get_outputs()[3].name,
            ],
            {
                self.decoder.get_inputs()[0].name: target,
                self.decoder.get_inputs()[1].name: target_len,
                self.decoder.get_inputs()[2].name: state0,
                self.decoder.get_inputs()[3].name: state1,
            },
        )
        return decoder_out, state0_next, state1_next

    def run_joiner(self, encoder_out: np.ndarray, decoder_out: np.ndarray):
        logit = self.joiner.run(
            [
                self.joiner.get_outputs()[0].name,
            ],
            {
                self.joiner.get_inputs()[0].name: encoder_out,
                self.joiner.get_inputs()[1].name: decoder_out,
            },
        )[0]
        return logit


def load_tokens(tokens_path="./tokens.txt"):
    id2token = dict()
    with open(tokens_path, encoding="utf-8") as f:
        for line in f:
            fields = line.strip().split()
            if len(fields) == 1:
                id2token[int(fields[0])] = " "
            else:
                t, idx = fields
                id2token[int(idx)] = t
    return id2token


# новая функция вместо transcribe_audio_to_json с новой оннх моделью
def transcribe_audio_to_json_onnx(model: OnnxModel, audio_file_path, id2token):
    # Здесь мы рассматриваем только одноканальное аудио (модель рассчитана на 1 канал)
    # Загружаем аудио (pydub можно убрать, тк мы работаем с numpy через soundfile)
    audio, sr = sf.read(audio_file_path, dtype="float32")
    if len(audio.shape) > 1:
        # если стерео, берём первый канал
        audio = audio[:, 0]
    if sr != 16000:
        audio = librosa.resample(audio, orig_sr=sr, target_sr=16000)
        sr = 16000

    # добавим padding 2 секунды тишины в конец
    tail_padding = np.zeros(sr * 2, dtype=np.float32)
    audio = np.concatenate([audio, tail_padding])

    fbank = create_fbank()
    features = compute_features(audio, fbank)

    blank = len(id2token) - 1
    ans = [blank]  # список индексов токенов
    state0, state1 = model.get_decoder_state()
    decoder_out, state0_next, state1_next = model.run_decoder(ans[-1], state0, state1)

    encoder_out = model.run_encoder(features)

    # decode loop
    for t in range(encoder_out.shape[2]):
        encoder_out_t = encoder_out[:, :, t : t + 1]
        logits = model.run_joiner(encoder_out_t, decoder_out)
        logits = torch.from_numpy(logits).squeeze()
        idx = torch.argmax(logits, dim=-1).item()
        if idx != blank:
            ans.append(idx)
            state0 = state0_next
            state1 = state1_next
            decoder_out, state0_next, state1_next = model.run_decoder(ans[-1], state0, state1)

    ans = ans[1:]  # удаляем первый blank токен

    tokens = [id2token.get(i, "") for i in ans]
    underline = "▁"
    text = "".join(tokens).replace(underline, " ").strip()

    # Вернём один "сегмент" с примерным временем от 0 до длины аудио в секундах
    audio_duration = len(audio) / sr

    result = [{
        "channel": "mono",
        "start": 0.0,
        "end": audio_duration,
        "text": text
    }]

    return result
   
def transcribe_channel_by_chunks(model: OnnxModel, audio_segment: AudioSegment, id2token, chunk_ms=60000):
    """
    Нарезаем audio_segment на куски chunk_ms мс (например, 1 минута = 60000 мс),
    транскрибируем каждый кусок, склеиваем результаты в один текст.
    """
    total_len = len(audio_segment)
    texts = []

    for start_ms in range(0, total_len, chunk_ms):
        end_ms = min(start_ms + chunk_ms, total_len)
        chunk = audio_segment[start_ms:end_ms]

        temp_file = f"temp_chunk_{start_ms}_{end_ms}.wav"
        chunk.export(temp_file, format="wav")

        res = transcribe_audio_to_json_onnx(model, temp_file, id2token)
        os.remove(temp_file)

        if res and len(res) > 0:
            text = res[0]["text"].strip()
            texts.append(text)

    full_text = " ".join(texts).strip()
    return full_text


def transcribe_stereo_onnx_by_chunks(model: OnnxModel, audio_file_path: str, id2token):
    audio = AudioSegment.from_file(audio_file_path)
    channels = audio.split_to_mono()
    
    if len(channels) == 0:
        # Почему-то нет каналов - вернуть пустые списки или исключение
        return [], []
    elif len(channels) == 1:
        # Если канал один, обрабатываем только его (левый, например)
        left_text = transcribe_channel_by_chunks(model, channels[0], id2token)
        return [{"channel": "left", "start": 0.0, "end": len(channels[0]) / 1000, "text": left_text}], []
    else:
        # Два и более канала, берем первые два
        left_text = transcribe_channel_by_chunks(model, channels[0], id2token)
        right_text = transcribe_channel_by_chunks(model, channels[1], id2token)

        left_result = [{
            "channel": "left",
            "start": 0.0,
            "end": len(channels[0]) / 1000,
            "text": left_text
        }]

        right_result = [{
            "channel": "right",
            "start": 0.0,
            "end": len(channels[1]) / 1000,
            "text": right_text
        }]

        return left_result, right_result
     
def transcribe_stereo_onnx_by_chunks22(model: OnnxModel, audio_file_path: str, id2token):
    audio = AudioSegment.from_file(audio_file_path)
    channels = audio.split_to_mono()

    left_text = transcribe_channel_by_chunks(model, channels[0], id2token)
    right_text = transcribe_channel_by_chunks(model, channels[1], id2token)

    left_result = [{
        "channel": "left",
        "start": 0.0,
        "end": len(channels[0]) / 1000,
        "text": left_text
    }]

    right_result = [{
        "channel": "right",
        "start": 0.0,
        "end": len(channels[1]) / 1000,
        "text": right_text
    }]

    return left_result, right_result
    
# Обновленная функция обработки и разделения каналов
def transcribe_stereo_onnx(model: OnnxModel, audio_file_path, id2token):
    audio = AudioSegment.from_file(audio_file_path)
    channels = audio.split_to_mono()

    results_left = []
    results_right = []

    for i, channel in enumerate(channels):
        temp_path = f"temp_channel_{i + 1}.wav"
        channel.export(temp_path, format="wav")
        res = transcribe_audio_to_json_onnx(model, temp_path, id2token)

        # уточняем channel-метку
        for r in res:
            r["channel"] = "left" if i == 0 else "right"

        if i == 0:
            results_left.extend(res)
        else:
            results_right.extend(res)

        # Можно удалить временный файл
        os.remove(temp_path)

    # Добавляем id
    for idx, item in enumerate(results_left):
        item["id"] = idx + 1
    for idx, item in enumerate(results_right):
        item["id"] = idx + 1

    return results_left, results_right

# Ниже оставляем вспомогательные функции download_audio, get_json_from_url, trim_file_path без изменений

def download_audio(url, save_folder, filename):
    os.makedirs(save_folder, exist_ok=True)
    file_path = os.path.join(save_folder, filename)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            with open(file_path, 'wb') as f:
                f.write(response.content)
            print(f"File saved: {file_path}")
        else:
            print(f"Failed to download file, status code: {response.status_code}")
    except Exception as e:
        print(f"Download error: {e}")

def get_json_from_url(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.json()
        else:
            return 0
    except Exception as e:
        return 0

def trim_file_path(file_path, path_to_remove):
    return file_path.replace(path_to_remove, "")


# обновленный цикл обработки URL
def check_url(url, model, id2token):
    save_folder = "./"
    filename = "audio.wav"
    curl = f"{url}/calls/queue.php"
    pymodel = SbertPuncCase()

    while True:
        try:
            
            result = get_json_from_url(curl)
            print(f" 1 {result}")
            if result != 0:
                """
                {
                "id":26395,
                "domain":"k2group.su",
                "file":"\/calls\/mp3\/k2group.su_509830.mp3",
                "activity":"354287",
                "channel":2,
                "recognize":0,
                "right":[],
                "left":[]
                }
                """
                mp3 = result['file']
                durl = f"{url}/{mp3}"
                
                print(f"2 download {durl}")
                
                download_audio(durl, save_folder, filename)

                # транскрибируем с новой моделью
                #result['left'], result['right'] = transcribe_stereo_onnx(model, filename, id2token)
                
                result['left'], result['right'] = transcribe_stereo_onnx_by_chunks(model, filename, id2token)
                #print(f" 31 {result['left']}")
                #print(f" 31 {result['right']}")
                
                if result.get('left') and len(result['left']) > 0 and result['left'][0].get('text'):
                    result['left'][0]['text'] = pymodel.punctuate(result['left'][0]['text'])
                else:
                    print("Left channel text missing")

                if result.get('right') and len(result['right']) > 0 and result['right'][0].get('text'):
                    result['right'][0]['text'] = pymodel.punctuate(result['right'][0]['text'])
                else:
                    print("Right channel text missing")
         
                print(f" 32 {result['left']}")
                print(f" 32 {result['right']}")
                surl = f"{url}/calls/updater.php"
                
                print(f" 4 {surl}")
                
                response = requests.post(surl, json=result)
                
                print("Status Code:", response.status_code)
            else:
                print("no data")
        except requests.exceptions.RequestException as e:
            print("wait")

        time.sleep(60)


if __name__ == "__main__":
    # Укажи пути к своим onnx моделям и токенам
    encoder_path = "/home/user/call_record/sber/transducer-giga/encoder.int8.onnx"
    decoder_path = "/home/user/call_record/sber/transducer-giga/decoder.onnx"
    joiner_path = "/home/user/call_record/sber/transducer-giga/joiner.onnx"
    tokens_path = "/home/user/call_record/sber/transducer-giga/tokens.txt"

    model_onnx = OnnxModel(encoder_path, decoder_path, joiner_path)
    id2token = load_tokens(tokens_path)

    url = "https://api.k2group.su"
    check_url(url, model_onnx, id2token)

Надо переделать чтобы он запускался и работал постоянно и на вход из другого скрипта принимал - путь к аудио файлу
 и отправлял массив левый и правый канал