import type {
  ChatMessageItemOption,
  ConversationOption,
  IChatEvent,
} from '@difizen/magent-chat';
import type { IChatMessageItem } from '@difizen/magent-chat';
import { ChatService } from '@difizen/magent-chat';
import { Fetcher } from '@difizen/magent-core';
import { inject, singleton } from '@difizen/mana-app';
import { EventSourceParserStream } from 'eventsource-parser/stream';

import type { APISession, SessionOption } from '../session/protocol.js';
import { toSessionOption } from '../session/protocol.js';

import type { APIMessage, AUMessageCreate } from './protocol.js';
import { AUChatEvent } from './protocol.js';

function stringToReadableStream(inputString: string) {
  // Convert the string into a Uint8Array
  const encoder = new TextEncoder();
  const uint8Array = encoder.encode(inputString);

  // Create a new ReadableStream
  const readableStream = new ReadableStream({
    start(controller) {
      // Enqueue the Uint8Array into the stream
      controller.enqueue(uint8Array);
      // Close the stream
      controller.close();
    },
  });

  return readableStream;
}
@singleton()
export class AUChatService extends ChatService {
  @inject(Fetcher) fetcher: Fetcher;
  override chat = async (option: AUMessageCreate): Promise<IChatMessageItem[]> => {
    const { agentId, sessionId, input } = option;
    const res = await this.fetcher.post<APIMessage>(
      `/api/v1/agents/${option.agentId}/chat`,
      {
        agent_id: agentId,
        session_id: sessionId,
        input: input,
      },
    );

    if (res.status === 200) {
      if (res.data.output) {
        return [
          {
            sender: { type: 'AI', id: agentId },
            content: res.data.output,
          },
        ];
      }
    }
    return [];
  };
  override chatStream = async (
    option: AUMessageCreate,
    messgeCallback: (event: IChatMessageItem) => void,
    eventCallback: (event: IChatEvent) => void,
  ) => {
    const { agentId, sessionId, input } = option;

    const url = `/api/v1/agents/${option.agentId}/stream-chat`;
    const msg = {
      agent_id: agentId,
      session_id: sessionId,
      input: input,
    };
    const res = await this.fetcher.post<ReadableStream<Uint8Array>>(url, msg, {
      headers: {
        Accept: 'text/event-stream',
      },
      responseType: 'stream',
      adapter: 'fetch',
    });
    if (res.status === 200) {
      let reader;
      if (typeof res.data === 'string') {
        reader = stringToReadableStream(res.data)
          .pipeThrough(new TextDecoderStream())
          .pipeThrough(new EventSourceParserStream())
          .getReader();
      } else {
        const stream = res.data;
        reader = stream
          .pipeThrough(new TextDecoderStream())
          .pipeThrough(new EventSourceParserStream())
          .getReader();
      }

      messgeCallback({
        sender: { type: 'AI', id: agentId },
        content: '',
      });
      let alreayDone = false;
      while (!alreayDone) {
        const { value, done } = await reader.read();
        if (done) {
          alreayDone = true;
          eventCallback({
            type: 'done',
          });

          break;
        }
        const data = JSON.parse(value.data);
        const event = AUChatEvent.format(value.event || 'chunk', data);
        eventCallback(event);
      }
      return;
    }
  };

  override getConversationMessages = async (
    conversation: ConversationOption,
  ): Promise<ChatMessageItemOption[]> => {
    return [];
  };

  override getConversations = async (opt: {
    agentId: string;
  }): Promise<SessionOption[]> => {
    const res = await this.fetcher.get<APISession[]>(`/api/v1/sessions`, {
      agent_id: opt.agentId,
    });
    if (res.status !== 200) {
      return [];
    }
    return res.data.map((item) => toSessionOption(item));
  };

  override getConversation = async (
    opt: ConversationOption,
  ): Promise<ConversationOption | undefined> => {
    return undefined;
  };

  override createConversation = async (option: {
    agentId: string;
  }): Promise<SessionOption> => {
    const res = await this.fetcher.post<APISession>(`/api/v1/sessions`, {
      agent_id: option.agentId,
    });
    if (res.status !== 200) {
      throw new Error('Create session failed');
    }
    return toSessionOption(res.data);
  };

  override deleteConversation = async (opt: ConversationOption): Promise<boolean> => {
    const res = await this.fetcher.delete<APISession>(`/api/v1/sessions/${opt.id}`);
    if (res.status !== 200) {
      return false;
    }
    return true;
  };
}
