import { AccountAddress, Aptos, Deserializer } from "@aptos-labs/ts-sdk";
import {
  Role,
  RoleAddedEvent,
  RoleRemovedEvent,
  OtcOfferCreatedEvent,
  OtcOfferFilledEvent,
  OtcOfferUpdatedEvent,
  OtcOfferCancelledEvent,
  EventWrapper,
  EventType,
  ConfigUpdatedEvent,
  EventResults,
} from "./type";
import { GraphQLClient, GraphQLConfig } from "./graphqlClient";

export class EventDeserializer {
  configUpdatedEvent(event: EventWrapper): ConfigUpdatedEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      sender: AccountAddress.deserialize(deserializer).toString(),
      fee_percent: deserializer.deserializeU64().toString(),
      fee_wallet: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  roleAddedEvent(event: EventWrapper): RoleAddedEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      sender: AccountAddress.deserialize(deserializer).toString(),
      user: AccountAddress.deserialize(deserializer).toString(),
      role: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  roleRemovedEvent(event: EventWrapper): RoleRemovedEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      sender: AccountAddress.deserialize(deserializer).toString(),
      user: AccountAddress.deserialize(deserializer).toString(),
      role: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  otcOfferCreatedEvent(event: EventWrapper): OtcOfferCreatedEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      order_id: AccountAddress.deserialize(deserializer).toString(),
      token_id: AccountAddress.deserialize(deserializer).toString(),
      owner: AccountAddress.deserialize(deserializer).toString(),
      ex_token: AccountAddress.deserialize(deserializer).toString(),
      value: deserializer.deserializeU64().toString(),
      deadline: deserializer.deserializeU64().toString(),
      is_buyer: deserializer.deserializeBool(),
      status: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  otcOfferFilledEvent(event: EventWrapper): OtcOfferFilledEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      buyer: AccountAddress.deserialize(deserializer).toString(),
      value: deserializer.deserializeU64().toString(),
      status: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  otcOfferUpdatedEvent(event: EventWrapper): OtcOfferUpdatedEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      buyer: AccountAddress.deserialize(deserializer).toString(),
      new_price: deserializer.deserializeU64().toString(),
      new_deadline: deserializer.deserializeU64().toString(),
      status: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  otcOfferCancelledEvent(event: EventWrapper): OtcOfferCancelledEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      status: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }
}

export class EventCrawler {
  aptos: Aptos;
  packageId: string;
  eventDeserializer: EventDeserializer;
  graphqlClient: GraphQLClient | null = null;

  constructor(aptos: Aptos, packageId: string, graphQlConfig?: GraphQLConfig) {
    this.aptos = aptos;
    this.packageId = packageId;
    this.eventDeserializer = new EventDeserializer();

    if (graphQlConfig) {
      this.graphqlClient = new GraphQLClient(graphQlConfig);
    }
  }

  public async getEvents(
    offset: number,
    limit: number = 25
  ): Promise<EventResults> {
    const events = await this.aptos.event.getModuleEventsByEventType({
      eventType: `${AccountAddress.fromStringStrict(
        this.packageId
      ).toStringShort()}::otc_pre_events::EventWrapper`,
      options: {
        offset,
        limit,
      },
    });

    const parsedEvents = events.map((event) =>
      this.parseEvent({
        ...event.data,
        block_height: event.transaction_block_height,
        transaction_version: event.transaction_version,
      })
    );
    return parsedEvents.filter((event) => event !== null);
  }

  public async getEventsV2(
    version: number = 0,
    limit: number = 50
  ): Promise<EventResults> {
    if (!this.graphqlClient) {
      throw new Error("GraphQL client not initialized");
    }

    const events = await this.graphqlClient.getEvents(
      version.toString(),
      limit
    );

    const parsedEvents = events.map((event) =>
      this.parseEvent({
        event_name: event.event_name,
        data: event.data,
        block_height: Number(event.block_height),
        transaction_version: Number(event.version),
      })
    );

    return parsedEvents.filter((event) => event !== null);
  }

  private parseEvent(event: EventWrapper) {
    switch (event.event_name) {
      case EventType.ConfigUpdatedEvent:
        return this.eventDeserializer.configUpdatedEvent(event);
      case EventType.RoleAddedEvent:
        return this.eventDeserializer.roleAddedEvent(event);
      case EventType.RoleRemovedEvent:
        return this.eventDeserializer.roleRemovedEvent(event);
      case EventType.OtcOfferCreatedEvent:
        return this.eventDeserializer.otcOfferCreatedEvent(event);
      case EventType.OtcOfferFilledEvent:
        return this.eventDeserializer.otcOfferFilledEvent(event);
      case EventType.OtcOfferUpdatedEvent:
        return this.eventDeserializer.otcOfferUpdatedEvent(event);
      case EventType.OtcOfferCancelledEvent:
        return this.eventDeserializer.otcOfferCancelledEvent(event);
      default:
        console.log(`Unknown event type: ${event.event_name}`);
        return null;
    }
  }
}
