import { AccountAddress, Aptos, Deserializer } from "@aptos-labs/ts-sdk";
import {
  Role,
  RoleAddedEvent,
  RoleRemovedEvent,
  ExTokenAddedEvent,
  ExTokenRemovedEvent,
  NewTokenEvent,
  NewOfferEvent,
  NewOrderEvent,
  SettleFilledEvent,
  SettleCancelledEvent,
  CancelOfferEvent,
  CloseOfferEvent,
  ForceCancelOrderEvent,
  UpdateTokenSettleDurationEvent,
  TokenToSettlePhaseEvent,
  TokenForceCancelSettlePhaseEvent,
  TokenStatusToggledEvent,
  TokenCancelledEvent,
  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(),
      discount_verifier_pubkey: AccountAddress.fromStrict(
        deserializer.deserializeFixedBytes(32)
      ).toString(),
      pledge_rate: deserializer.deserializeU64().toString(),
      fee_settle: deserializer.deserializeU64().toString(),
      fee_refund: 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,
    };
  }

  exTokenAddedEvent(event: EventWrapper): ExTokenAddedEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      sender: AccountAddress.deserialize(deserializer).toString(),
      token: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  exTokenRemovedEvent(event: EventWrapper): ExTokenRemovedEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      sender: AccountAddress.deserialize(deserializer).toString(),
      token: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  newTokenEvent(event: EventWrapper): NewTokenEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      token_id: AccountAddress.deserialize(deserializer).toString(),
      token_index: deserializer.deserializeU64().toString(),
      settle_duration: deserializer.deserializeU64().toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  newOfferEvent(event: EventWrapper): NewOfferEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      offer_type: deserializer.deserializeU8(),
      token_id: AccountAddress.deserialize(deserializer).toString(),
      token_index: deserializer.deserializeU64().toString(),
      amount: deserializer.deserializeU64().toString(),
      value: deserializer.deserializeU64().toString(),
      collateral: deserializer.deserializeU64().toString(),
      full_match: deserializer.deserializeBool(),
      doer: AccountAddress.deserialize(deserializer).toString(),
      ex_token: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  newOrderEvent(event: EventWrapper): NewOrderEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      order_id: AccountAddress.deserialize(deserializer).toString(),
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      amount: deserializer.deserializeU64().toString(),
      seller: AccountAddress.deserialize(deserializer).toString(),
      buyer: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  settleFilledEvent(event: EventWrapper): SettleFilledEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      order_id: AccountAddress.deserialize(deserializer).toString(),
      value: deserializer.deserializeU64().toString(),
      fee: deserializer.deserializeU64().toString(),
      doer: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  settleCancelledEvent(event: EventWrapper): SettleCancelledEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      order_id: AccountAddress.deserialize(deserializer).toString(),
      value: deserializer.deserializeU64().toString(),
      fee: deserializer.deserializeU64().toString(),
      doer: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  cancelOfferEvent(event: EventWrapper): CancelOfferEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      refund_value: deserializer.deserializeU64().toString(),
      refund_fee: deserializer.deserializeU64().toString(),
      doer: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  closeOfferEvent(event: EventWrapper): CloseOfferEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      offer_id: AccountAddress.deserialize(deserializer).toString(),
      refund_amount: deserializer.deserializeU64().toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  forceCancelOrderEvent(event: EventWrapper): ForceCancelOrderEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      order_id: AccountAddress.deserialize(deserializer).toString(),
      doer: AccountAddress.deserialize(deserializer).toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  updateTokenSettleDurationEvent(
    event: EventWrapper
  ): UpdateTokenSettleDurationEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      token_id: AccountAddress.deserialize(deserializer).toString(),
      token_index: deserializer.deserializeU64().toString(),
      old_value: deserializer.deserializeU64().toString(),
      new_value: deserializer.deserializeU64().toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  tokenToSettlePhaseEvent(event: EventWrapper): TokenToSettlePhaseEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      token_id: AccountAddress.deserialize(deserializer).toString(),
      token_index: deserializer.deserializeU64().toString(),
      token: AccountAddress.deserialize(deserializer).toString(),
      settle_rate: deserializer.deserializeU64().toString(),
      settle_time: deserializer.deserializeU64().toString(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  tokenForceCancelSettlePhaseEvent(
    event: EventWrapper
  ): TokenForceCancelSettlePhaseEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      token_id: AccountAddress.deserialize(deserializer).toString(),
      token_index: deserializer.deserializeU64().toString(),
      status: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  tokenStatusToggledEvent(event: EventWrapper): TokenStatusToggledEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      token_id: AccountAddress.deserialize(deserializer).toString(),
      token_index: deserializer.deserializeU64().toString(),
      old_status: deserializer.deserializeU8(),
      new_status: deserializer.deserializeU8(),
      block_height: event.block_height,
      transaction_version: event.transaction_version,
    };
  }

  tokenCancelledEvent(event: EventWrapper): TokenCancelledEvent {
    const deserializer = Deserializer.fromHex(event.data);
    return {
      event_name: event.event_name,
      token_id: AccountAddress.deserialize(deserializer).toString(),
      token_index: deserializer.deserializeU64().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()}::event_config::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.ExTokenAddedEvent:
        return this.eventDeserializer.exTokenAddedEvent(event);
      case EventType.ExTokenRemovedEvent:
        return this.eventDeserializer.exTokenRemovedEvent(event);
      case EventType.NewTokenEvent:
        return this.eventDeserializer.newTokenEvent(event);
      case EventType.NewOfferEvent:
        return this.eventDeserializer.newOfferEvent(event);
      case EventType.NewOrderEvent:
        return this.eventDeserializer.newOrderEvent(event);
      case EventType.SettleFilledEvent:
        return this.eventDeserializer.settleFilledEvent(event);
      case EventType.SettleCancelledEvent:
        return this.eventDeserializer.settleCancelledEvent(event);
      case EventType.CancelOfferEvent:
        return this.eventDeserializer.cancelOfferEvent(event);
      case EventType.CloseOfferEvent:
        return this.eventDeserializer.closeOfferEvent(event);
      case EventType.ForceCancelOrderEvent:
        return this.eventDeserializer.forceCancelOrderEvent(event);
      case EventType.UpdateTokenSettleDurationEvent:
        return this.eventDeserializer.updateTokenSettleDurationEvent(event);
      case EventType.TokenToSettlePhaseEvent:
        return this.eventDeserializer.tokenToSettlePhaseEvent(event);
      case EventType.TokenForceCancelSettlePhaseEvent:
        return this.eventDeserializer.tokenForceCancelSettlePhaseEvent(event);
      case EventType.TokenStatusToggledEvent:
        return this.eventDeserializer.tokenStatusToggledEvent(event);
      case EventType.TokenCancelledEvent:
        return this.eventDeserializer.tokenCancelledEvent(event);
      default:
        console.log(`Unknown event type: ${event.event_name}`);
        return null;
    }
  }
}
