import { writeGeneratedFile } from "../../helpers/fs";
import { generateFileHeader } from "../types/fileHeader";
import { generateWebhookEventFunctionForList } from "../webhookEventFactory";

const FILE_NAME = "record.ts";

export function generateRecordFetcher(outputDir: string): void {
  const content = `${generateFileHeader(FILE_NAME)}
import { AttioClient } from "./attioClient";
import { BaseFetcher } from "./base";
import { AttioWebhookEventPopulatedListener } from "./webhook";
import { AttioRecord, AttioAttributeValue, AttioRecordListEntry, WebhookEventDataByType } from "../types";

export interface RecordFilters {
  limit?: number;
  offset?: string;
  sort?: Record<string, any>[];
  filter?: Record<string, any>;
}

export interface AttioRecordInput {
  data: {
    values: Record<string, any>;
  };
}

export class AttioRecordFetcher<T = Record<string, any>, I = Record<string, any>, A = Record<string, any>> extends BaseFetcher {
  constructor(
    client: AttioClient,
    private readonly objectIdentifier: string,
  ) {
    super(client);
  }

  protected createBaseUrl(): string {
    return \`/objects/$\{this.objectIdentifier}/records\`;
  }

  async getAll(filters?: RecordFilters) {
    const data = await this.extractData<AttioRecord<T>[]>(
      this.client.doFetch(\`$\{this.createBaseUrl()}/query\`, {
        method: "POST",
        body: filters,
      }),
    );
    return this.mapRecords(data);
  }

  async getById(id: string) {
    const data = await this.extractData<AttioRecord<T>>(
      this.client.doFetch(\`$\{this.createBaseUrl()}/$\{id}\`),
    );
    return this.mapRecord(data);
  }

  async create(input: I) {
    const data = await this.extractData<AttioRecord<T>>(
      this.client.doFetch(this.createBaseUrl(), {
        method: "POST",
        body: {
          data: {
            values: input,
          }
        },
      }),
    );
    return this.mapRecord(data);
  }

  async assertRecordExists(matchingAttribute: string, data: AttioRecordInput): Promise<void> {
    await this.client.doFetch(this.createBaseUrl(), {
      method: "PUT",
      body: data,
      query: { matching_attribute: matchingAttribute },
    });
  }

  async update(id: string, input: I, { overwriteMultiSelect }: { overwriteMultiSelect?: boolean } = {}) {
    const data = await this.extractData<AttioRecord<T>>(
      this.client.doFetch(\`$\{this.createBaseUrl()}/$\{id}\`, {
        method: overwriteMultiSelect ? "PUT" : "PATCH",
        body: {
          data: {
            values: input,
          },
        },
      }),
    );
    return this.mapRecord(data);
  }

  async delete(id: string): Promise<void> {
    await this.client.doFetch(\`$\{this.createBaseUrl()}/$\{id}\`, {
      method: "DELETE",
    });
  }

  async listAttributeValues(
    id: string,
    attributeId: string,
    query: {
      showHistoric?: boolean;
      limit?: number;
      offset?: number;
    } = {},
  ): Promise<A[]> {
    const values = await this.extractData<AttioAttributeValue[]>(
      this.client.doFetch(\`$\{this.createBaseUrl()}/$\{id}/attributes/$\{attributeId}/values\`, {
        query: {
          show_historic: query.showHistoric,
          limit: query.limit,
          offset: query.offset,
        },
      }),
    );
    return this.mapValues(values);
  }

  async listEntries(
    id: string,
    query: {
      limit?: number;
      offset?: number;
    } = {},
  ): Promise<AttioRecordListEntry[]> {
    return this.extractData(
      this.client.doFetch(\`$\{this.createBaseUrl()}/$\{id}/entries\`, {
        query,
      }),
    );
  }

  ${generateWebhookEventFunctionForList({
    eventNames: ["record.created", "record.updated", "record.deleted", "record.merged"],
    idKey: "record_id",
    populatedType: "A",
  })}

  private stripAttioAttributeValueKeys(value: AttioAttributeValue) {
    const { active_from, active_until, created_by_actor, attribute_type, ...rest } = value;
    return rest;
  }

  private parseValue(value: AttioAttributeValue) {
    if (!value) {
      return null;
    }

    if (['location', 'domain', 'phone-number', 'record-reference', 'actor-reference'].includes(value.attribute_type)) {
      return this.stripAttioAttributeValueKeys(value);
    }
    
    if (value.attribute_type === 'select') {
      return value.option ?  {
        id: value.option.id.option_id,
        title: value.option.title,
        is_archived: value.option.is_archived,
      } : null;
    }

    return value?.value;
  }

  private mapValues(values: AttioAttributeValue[]) {
    const now = new Date();

    const filtered = values.filter((v: any) => {
      const activeFrom = new Date(v.active_from);
      const activeUntil = v.active_until === null ? null : new Date(v.active_until);

      if (activeUntil === null) {
        return activeFrom <= now;
      }

      return activeFrom <= now && activeUntil >= now;
    });

    if (filtered.length === 0) {
      return null;
    }

    if (filtered.length === 1) {
      return this.parseValue(filtered[0]);
    }

    return filtered.map((v: AttioAttributeValue) => this.parseValue(v));
  }

  private mapRecord({ id, created_at, updated_at, values }: AttioRecord<T>): A {
    return {
      record_id: id.record_id,
      created_at,
      updated_at,
      ...Object.entries(values as Record<string, any>).reduce((acc, curr) => {
        return {
          ...acc,
          [curr[0]]: this.mapValues(curr[1] as AttioAttributeValue[])
        };
      }, {}),
    } as A;
  }

  private mapRecords(records: AttioRecord<T>[]) {
    return records.map(record => this.mapRecord(record));
  }
}`;

  writeGeneratedFile(outputDir, FILE_NAME, content);
}
