import { createTwirpRequest, throwTwirpError, Fetch } from "./twirp";

export interface GetItemsRequest {
  serviceId: string;
  stageId: string;
  start: Date;
  end: Date;
  skipPartialDataRefetch: boolean;
  maxTrendPoints: number;
}

interface GetItemsRequestJSON {
  service_id: string;
  stage_id: string;
  start: string;
  end: string;
  skip_partial_data_refetch: boolean;
  max_trend_points: number;
}

const GetItemsRequestToJSON = (m: GetItemsRequest): GetItemsRequestJSON => {
  return {
    service_id: m.serviceId,
    stage_id: m.stageId,
    start: m.start.toISOString(),
    end: m.end.toISOString(),
    skip_partial_data_refetch: m.skipPartialDataRefetch,
    max_trend_points: m.maxTrendPoints,
  };
};

export interface GetItemsResponse {
  items: Item[];
  fetched: Date;
  start: Date;
  end: Date;
  queryTimeMs: number;
}

interface GetItemsResponseJSON {
  items: ItemJSON[];
  fetched: string;
  start: string;
  end: string;
  query_time_ms: number;
}

const JSONToGetItemsResponse = (
  m: GetItemsResponse | GetItemsResponseJSON
): GetItemsResponse => {
  return {
    items: (m.items as (Item | ItemJSON)[]).map(JSONToItem),
    fetched: new Date(m.fetched),
    start: new Date(m.start),
    end: new Date(m.end),
    queryTimeMs: (m as GetItemsResponse).queryTimeMs
      ? (m as GetItemsResponse).queryTimeMs
      : (m as GetItemsResponseJSON).query_time_ms,
  };
};

export interface Item {
  fingerprint: string;
  trend: Trend;
  lastOccurrence: Occurrence;
  numOccurrences: number;
}

interface ItemJSON {
  fingerprint: string;
  trend: TrendJSON;
  last_occurrence: OccurrenceJSON;
  num_occurrences: number;
}

const JSONToItem = (m: Item | ItemJSON): Item => {
  return {
    fingerprint: m.fingerprint,
    trend: JSONToTrend(m.trend),
    lastOccurrence: JSONToOccurrence(
      (m as Item).lastOccurrence
        ? (m as Item).lastOccurrence
        : (m as ItemJSON).last_occurrence
    ),
    numOccurrences: (m as Item).numOccurrences
      ? (m as Item).numOccurrences
      : (m as ItemJSON).num_occurrences,
  };
};

export interface Trend {
  points: TrendPoint[];
  bucketSize: number;
}

interface TrendJSON {
  points: TrendPointJSON[];
  bucket_size: number;
}

const JSONToTrend = (m: Trend | TrendJSON): Trend => {
  return {
    points: (m.points as (TrendPoint | TrendPointJSON)[]).map(JSONToTrendPoint),
    bucketSize: (m as Trend).bucketSize
      ? (m as Trend).bucketSize
      : (m as TrendJSON).bucket_size,
  };
};

export interface TrendPoint {
  start: Date;
  count: number;
}

interface TrendPointJSON {
  start: string;
  count: number;
}

const JSONToTrendPoint = (m: TrendPoint | TrendPointJSON): TrendPoint => {
  return {
    start: new Date(m.start),
    count: m.count,
  };
};

export interface GetOccurrencesRequest {
  serviceId: string;
  stageId: string;
  fingerprint: string;
  start: Date;
  end: Date;
  skipPartialDataRefetch: boolean;
}

interface GetOccurrencesRequestJSON {
  service_id: string;
  stage_id: string;
  fingerprint: string;
  start: string;
  end: string;
  skip_partial_data_refetch: boolean;
}

const GetOccurrencesRequestToJSON = (
  m: GetOccurrencesRequest
): GetOccurrencesRequestJSON => {
  return {
    service_id: m.serviceId,
    stage_id: m.stageId,
    fingerprint: m.fingerprint,
    start: m.start.toISOString(),
    end: m.end.toISOString(),
    skip_partial_data_refetch: m.skipPartialDataRefetch,
  };
};

export interface GetOccurrencesResponse {
  fingerprint: string;
  occurrence: Occurrence[];
  trendDay: Trend;
  queryTimeMs: number;
}

interface GetOccurrencesResponseJSON {
  fingerprint: string;
  occurrence: OccurrenceJSON[];
  trend_day: TrendJSON;
  query_time_ms: number;
}

const JSONToGetOccurrencesResponse = (
  m: GetOccurrencesResponse | GetOccurrencesResponseJSON
): GetOccurrencesResponse => {
  return {
    fingerprint: m.fingerprint,
    occurrence: (m.occurrence as (Occurrence | OccurrenceJSON)[]).map(
      JSONToOccurrence
    ),
    trendDay: JSONToTrend(
      (m as GetOccurrencesResponse).trendDay
        ? (m as GetOccurrencesResponse).trendDay
        : (m as GetOccurrencesResponseJSON).trend_day
    ),
    queryTimeMs: (m as GetOccurrencesResponse).queryTimeMs
      ? (m as GetOccurrencesResponse).queryTimeMs
      : (m as GetOccurrencesResponseJSON).query_time_ms,
  };
};

export interface Occurrence {
  occurrenceId: string;
  service: string;
  fingerprint: string;
  timestamp: Date;
  level: string;
  message: string;
  rawJson: string;
  stackTrace: StackTrace[];
  requestId: string;
  operation: string;
  dependency: string;
}

interface OccurrenceJSON {
  occurrence_id: string;
  service: string;
  fingerprint: string;
  timestamp: string;
  level: string;
  message: string;
  raw_json: string;
  stack_trace: StackTraceJSON[];
  request_id: string;
  operation: string;
  dependency: string;
}

const JSONToOccurrence = (m: Occurrence | OccurrenceJSON): Occurrence => {
  return {
    occurrenceId: (m as Occurrence).occurrenceId
      ? (m as Occurrence).occurrenceId
      : (m as OccurrenceJSON).occurrence_id,
    service: m.service,
    fingerprint: m.fingerprint,
    timestamp: new Date(m.timestamp),
    level: m.level,
    message: m.message,
    rawJson: (m as Occurrence).rawJson
      ? (m as Occurrence).rawJson
      : (m as OccurrenceJSON).raw_json,
    stackTrace: (((m as Occurrence).stackTrace
      ? (m as Occurrence).stackTrace
      : (m as OccurrenceJSON).stack_trace) as (
      | StackTrace
      | StackTraceJSON
    )[]).map(JSONToStackTrace),
    requestId: (m as Occurrence).requestId
      ? (m as Occurrence).requestId
      : (m as OccurrenceJSON).request_id,
    operation: m.operation,
    dependency: m.dependency,
  };
};

export interface StackTrace {
  fullFilePath: string;
  line: number;
  method: string;
  shortFilePath: string;
  brazilPkgName: string;
  relevant: boolean;
  lineUrl: string;
  pkgUrl: string;
}

interface StackTraceJSON {
  full_file_path: string;
  line: number;
  method: string;
  short_file_path: string;
  brazil_pkg_name: string;
  relevant: boolean;
  line_url: string;
  pkg_url: string;
}

const JSONToStackTrace = (m: StackTrace | StackTraceJSON): StackTrace => {
  return {
    fullFilePath: (m as StackTrace).fullFilePath
      ? (m as StackTrace).fullFilePath
      : (m as StackTraceJSON).full_file_path,
    line: m.line,
    method: m.method,
    shortFilePath: (m as StackTrace).shortFilePath
      ? (m as StackTrace).shortFilePath
      : (m as StackTraceJSON).short_file_path,
    brazilPkgName: (m as StackTrace).brazilPkgName
      ? (m as StackTrace).brazilPkgName
      : (m as StackTraceJSON).brazil_pkg_name,
    relevant: m.relevant,
    lineUrl: (m as StackTrace).lineUrl
      ? (m as StackTrace).lineUrl
      : (m as StackTraceJSON).line_url,
    pkgUrl: (m as StackTrace).pkgUrl
      ? (m as StackTrace).pkgUrl
      : (m as StackTraceJSON).pkg_url,
  };
};

export interface GetConfigRequest {}

interface GetConfigRequestJSON {}

const GetConfigRequestToJSON = (m: GetConfigRequest): GetConfigRequestJSON => {
  return {};
};

export interface GetConfigResponse {
  services: Service[];
  version: string;
  gitCommit: string;
}

interface GetConfigResponseJSON {
  services: ServiceJSON[];
  version: string;
  git_commit: string;
}

const JSONToGetConfigResponse = (
  m: GetConfigResponse | GetConfigResponseJSON
): GetConfigResponse => {
  return {
    services: (m.services as (Service | ServiceJSON)[]).map(JSONToService),
    version: m.version,
    gitCommit: (m as GetConfigResponse).gitCommit
      ? (m as GetConfigResponse).gitCommit
      : (m as GetConfigResponseJSON).git_commit,
  };
};

export interface Service {
  serviceId: string;
  stageId: string;
  urlSlug: string;
  displayName: string;
  displayStage: string;
}

interface ServiceJSON {
  service_id: string;
  stage_id: string;
  url_slug: string;
  display_name: string;
  display_stage: string;
}

const JSONToService = (m: Service | ServiceJSON): Service => {
  return {
    serviceId: (m as Service).serviceId
      ? (m as Service).serviceId
      : (m as ServiceJSON).service_id,
    stageId: (m as Service).stageId
      ? (m as Service).stageId
      : (m as ServiceJSON).stage_id,
    urlSlug: (m as Service).urlSlug
      ? (m as Service).urlSlug
      : (m as ServiceJSON).url_slug,
    displayName: (m as Service).displayName
      ? (m as Service).displayName
      : (m as ServiceJSON).display_name,
    displayStage: (m as Service).displayStage
      ? (m as Service).displayStage
      : (m as ServiceJSON).display_stage,
  };
};

export interface Plucky {
  getItems: (getItemsRequest: GetItemsRequest) => Promise<GetItemsResponse>;

  getOccurrences: (
    getOccurrencesRequest: GetOccurrencesRequest
  ) => Promise<GetOccurrencesResponse>;

  getConfig: (getConfigRequest: GetConfigRequest) => Promise<GetConfigResponse>;
}

export class DefaultPlucky implements Plucky {
  private hostname: string;
  private fetch: Fetch;
  private writeCamelCase: boolean;
  private pathPrefix = "/twirp/plucky.Plucky/";

  constructor(hostname: string, fetch: Fetch, writeCamelCase = false) {
    this.hostname = hostname;
    this.fetch = fetch;
    this.writeCamelCase = writeCamelCase;
  }
  getItems(getItemsRequest: GetItemsRequest): Promise<GetItemsResponse> {
    const url = this.hostname + this.pathPrefix + "GetItems";
    let body: GetItemsRequest | GetItemsRequestJSON = getItemsRequest;
    if (!this.writeCamelCase) {
      body = GetItemsRequestToJSON(getItemsRequest);
    }
    return this.fetch(createTwirpRequest(url, body)).then((resp) => {
      if (!resp.ok) {
        return throwTwirpError(resp);
      }

      return resp.json().then(JSONToGetItemsResponse);
    });
  }

  getOccurrences(
    getOccurrencesRequest: GetOccurrencesRequest
  ): Promise<GetOccurrencesResponse> {
    const url = this.hostname + this.pathPrefix + "GetOccurrences";
    let body:
      | GetOccurrencesRequest
      | GetOccurrencesRequestJSON = getOccurrencesRequest;
    if (!this.writeCamelCase) {
      body = GetOccurrencesRequestToJSON(getOccurrencesRequest);
    }
    return this.fetch(createTwirpRequest(url, body)).then((resp) => {
      if (!resp.ok) {
        return throwTwirpError(resp);
      }

      return resp.json().then(JSONToGetOccurrencesResponse);
    });
  }

  getConfig(getConfigRequest: GetConfigRequest): Promise<GetConfigResponse> {
    const url = this.hostname + this.pathPrefix + "GetConfig";
    let body: GetConfigRequest | GetConfigRequestJSON = getConfigRequest;
    if (!this.writeCamelCase) {
      body = GetConfigRequestToJSON(getConfigRequest);
    }
    return this.fetch(createTwirpRequest(url, body)).then((resp) => {
      if (!resp.ok) {
        return throwTwirpError(resp);
      }

      return resp.json().then(JSONToGetConfigResponse);
    });
  }
}
