import { TalerExchangeHttpClient } from "../http-client/exchange-client.js";
import { AbsoluteTime, Duration } from "../time.js";
import { OfficerAccount } from "../types-taler-common.js";
import { TOPS_AmlEventsName } from "./events.js";

/**
 * Define a set of parameters to make a request to the server
 */
export type EventQuery<Ev> = {
  event: Ev;
  start: AbsoluteTime | undefined;
  end: AbsoluteTime | undefined;
};

/**
 * Map between a name and a request parameter
 */
export type QueryModel<Ev> = {
  [name: string]: EventQuery<Ev>;
};

/**
 * All the request needed to create the Event Reporting (TOPS)
 * https://docs.taler.net/deployments/tops.html#event-reporting-tops
 *
 * Maps a request key to request parameters
 *
 */
export const EventReporting_TOPS_queries = {
  // Number of accounts that are opened
  accounts_open_incr: {
    event: TOPS_AmlEventsName.INCR_ACCOUNT_OPEN,
    start: undefined,
    end: undefined,
  },
  accounts_open_decr: {
    event: TOPS_AmlEventsName.DECR_ACCOUNT_OPEN,
    start: undefined,
    end: undefined,
  },
  // Number of new GwG files in the last year
  gwg_files_new_last_year: {
    event: TOPS_AmlEventsName.INCR_ACCOUNT_OPEN,
    start: AbsoluteTime.addDuration(
      AbsoluteTime.now(),
      Duration.fromSpec({ years: -1 }),
    ),
    end: AbsoluteTime.now(),
  },
  // Number of GwG files closed in the last year
  gwg_files_closed_last_year: {
    event: TOPS_AmlEventsName.DECR_ACCOUNT_OPEN,
    start: AbsoluteTime.addDuration(
      AbsoluteTime.now(),
      Duration.fromSpec({ years: -1 }),
    ),
    end: AbsoluteTime.now(),
  },
  // Number of GwG files of high-risk customers
  gwg_files_high_risk_incr: {
    event: TOPS_AmlEventsName.INCR_HIGH_RISK_CUSTOMER, //FIXME: spec refers to INCR_HIGH_RISK
    start: undefined,
    end: undefined,
  },
  gwg_files_high_risk_decr: {
    event: TOPS_AmlEventsName.DECR_HIGH_RISK_CUSTOMER,
    start: undefined,
    end: undefined,
  },
  // Number of GwG files managed with “increased risk” due to PEP status
  gwg_files_pep_incr: {
    event: TOPS_AmlEventsName.INCR_PEP,
    start: undefined,
    end: undefined,
  },
  gwg_files_pep_decr: {
    event: TOPS_AmlEventsName.DECR_PEP,
    start: undefined,
    end: undefined,
  },
  // Number of MROS reports based on Art 9 Abs. 1 GwG (per year)
  mros_reports_art9_last_year: {
    event: TOPS_AmlEventsName.MROS_REPORTED_SUSPICION_SUBSTANTIATED,
    start: AbsoluteTime.addDuration(
      AbsoluteTime.now(),
      Duration.fromSpec({ years: -1 }),
    ),
    end: AbsoluteTime.now(),
  },
  // Number of MROS reports based on Art 305ter Abs. 2 StGB (per year)
  mros_reports_art305_last_year: {
    event: TOPS_AmlEventsName.MROS_REPORTED_SUSPICION_SIMPLE,
    start: AbsoluteTime.addDuration(
      AbsoluteTime.now(),
      Duration.fromSpec({ years: -1 }),
    ),
    end: AbsoluteTime.now(),
  },
  // Number of customers involved in proceedings for which Art 6 GwG did apply
  accounts_involed_in_proceedings_last_year: {
    event: TOPS_AmlEventsName.INCR_INVESTIGATION_CONCLUDED, //FIXME: spec refers to INCR_INVESTIGATION
    start: AbsoluteTime.addDuration(
      AbsoluteTime.now(),
      Duration.fromSpec({ years: -1 }),
    ),
    end: AbsoluteTime.now(),
  },
} satisfies QueryModel<TOPS_AmlEventsName>;

/**
 * All the calculation needed to create the Event Reporting (TOPS)
 * https://docs.taler.net/deployments/tops.html#event-reporting-tops
 *
 * Maps a event reporting name with a calculation which uses the
 * result of a query to the server.
 *
 * @param events The result of event reporting query
 * @returns
 */
export const EventReporting_TOPS_calculation = (
  events: CounterResultByEventName<typeof EventReporting_TOPS_queries>,
) =>
  ({
    //   Number of accounts that are opened:
    accounts_opened: safeSub(
      events.accounts_open_incr,
      events.accounts_open_decr,
    ),

    // Number of new GwG files in the last year.
    new_gwg_files_last_year: events.gwg_files_new_last_year,

    // Number of GwG files closed in the last year
    gwg_files_closed_last_year: events.gwg_files_closed_last_year,

    // Number of GwG files of high-risk customers
    gwg_files_high_risk: safeSub(
      events.gwg_files_high_risk_incr,
      events.gwg_files_high_risk_decr,
    ),

    // Number of GwG files managed with “increased risk” due to PEP status
    gwg_files_pep: safeSub(
      events.gwg_files_pep_incr,
      events.gwg_files_pep_decr,
    ),

    // Number of MROS reports based on Art 9 Abs. 1 GwG (per year)
    mros_reports_art9_last_year: events.mros_reports_art9_last_year,

    // Number of MROS reports based on Art 305ter Abs. 2 StGB (per year)
    mros_reports_art305_last_year: events.mros_reports_art305_last_year,

    // Number of customers involved in proceedings for which Art 6 GwG did apply
    accounts_involed_in_proceedings_last_year:
      events.accounts_involed_in_proceedings_last_year,
  }) as const;

/**
 * All the request needed to create the Event Reporting (CQF)
 * https://docs.taler.net/deployments/tops.html#event-reporting-vqf
 *
 * Maps a request key to request parameters.
 * Requires the times for the query range.
 *
 */
export const EventReporting_VQF_queries = (
  jan_1st: AbsoluteTime,
  dec_31st: AbsoluteTime,
) => {
  const zero = AbsoluteTime.fromMilliseconds(0);

  return {
    // Number of open accounts on January 1st (self-declaration 3.1.1)
    accounts_open_first_jan_incr: {
      event: TOPS_AmlEventsName.INCR_ACCOUNT_OPEN,
      start: zero,
      end: jan_1st,
    },
    accounts_open_first_jan_decr: {
      event: TOPS_AmlEventsName.INCR_ACCOUNT_OPEN,
      start: zero,
      end: jan_1st,
    },
    // Number of newly opened accounts between 01.01.20XX and 31.12.20XX (self-declaration 3.1.2.)
    accounts_opened_on_year: {
      event: TOPS_AmlEventsName.INCR_ACCOUNT_OPEN,
      start: jan_1st,
      end: dec_31st,
    },
    // Number of AML files managed during the year 20XX (self-declaration 3.1.3.)
    aml_files_managed_on_year_incr: {
      event: TOPS_AmlEventsName.INCR_ACCOUNT_OPEN,
      start: zero,
      end: dec_31st,
    },
    aml_files_managed_on_year_decr: {
      event: TOPS_AmlEventsName.DECR_ACCOUNT_OPEN,
      start: zero,
      end: jan_1st,
    },
    // Number of AML files closed between 01.01.20XX and 31.12.20XX (self-declaration 3.1.4)
    aml_files_closed_on_year: {
      event: TOPS_AmlEventsName.DECR_ACCOUNT_OPEN,
      start: jan_1st,
      end: dec_31st,
    },
    // Were there business relationships in the year 20XX with high risk? (self-declaration 4.1)
    accounts_high_risk_incr: {
      event: TOPS_AmlEventsName.INCR_HIGH_RISK_CUSTOMER,
      start: zero,
      end: dec_31st,
    },
    accounts_high_risk_decr: {
      event: TOPS_AmlEventsName.DECR_HIGH_RISK_CUSTOMER,
      start: zero,
      end: dec_31st,
    },
    // Of those, how many were with PEPs? (self-declaration 4.2.)
    accounts_pep_incr: {
      event: TOPS_AmlEventsName.INCR_PEP,
      start: zero,
      end: dec_31st,
    },
    accounts_pep_decr: {
      event: TOPS_AmlEventsName.DECR_PEP,
      start: zero,
      end: dec_31st,
    },
    // Of those PEPs, how many were with foreign PEPs? (self-declaration 4.3.)
    accounts_pep_foreign_incr: {
      event: TOPS_AmlEventsName.INCR_PEP_FOREIGN,
      start: zero,
      end: dec_31st,
    },
    accounts_pep_foreign_decr: {
      event: TOPS_AmlEventsName.DECR_PEP_FOREIGN,
      start: zero,
      end: dec_31st,
    },
    // Number of other additional (other than PEPs and foreign PEPs) high-risk business relationships in 20XX
    // comment: no need to add extra query
    //
    // Number of high-risk business relationship n total in 20xx (self-declaration 4.5.)
    // comment: we have this information already
    //
    // Number of reports (substantiated suspicion) to MROS during 20xx (self-declaration 5.1)
    mros_suspicion_substantiated: {
      event: TOPS_AmlEventsName.MROS_REPORTED_SUSPICION_SUBSTANTIATED,
      start: jan_1st,
      end: dec_31st,
    },
    // Number of reports (simple suspicion) to MROS during 20xx (self-declaration 5.2)
    mros_suspicion_simple: {
      event: TOPS_AmlEventsName.MROS_REPORTED_SUSPICION_SIMPLE,
      start: jan_1st,
      end: dec_31st,
    },
    // Total number of reports to MROS during 20xx (self-declaration 5.3)
    // comment: no need to add extra query
  } satisfies QueryModel<TOPS_AmlEventsName>;
};

export const EventReporting_VQF_calculation = (
  events: CounterResultByEventName<
    ReturnType<typeof EventReporting_VQF_queries>
  >,
) => {
  return {
    // Number of open accounts on January 1st (self-declaration 3.1.1)
    accounts_open_first_jan: safeSub(
      events.accounts_open_first_jan_incr,
      events.accounts_open_first_jan_decr,
    ),

    // Number of newly opened accounts between 01.01.20XX and 31.12.20XX (self-declaration 3.1.2.)
    accounts_opened_on_year: events.accounts_opened_on_year,

    // Number of AML files managed during the year 20XX (self-declaration 3.1.3.)
    aml_files_managed_on_year: safeSub(
      events.aml_files_managed_on_year_incr,
      events.aml_files_managed_on_year_decr,
    ),

    // Number of AML files closed between 01.01.20XX and 31.12.20XX (self-declaration 3.1.4)
    aml_files_closed_on_year: events.aml_files_closed_on_year,

    // Were there business relationships in the year 20XX with high risk? (self-declaration 4.1)
    accounts_high_risk: safeSub(
      events.accounts_high_risk_incr,
      events.accounts_high_risk_decr,
    ),

    // Of those, how many were with PEPs? (self-declaration 4.2.)
    accounts_pep: safeSub(events.accounts_pep_incr, events.accounts_pep_decr),

    // Of those PEPs, how many were with foreign PEPs? (self-declaration 4.3.)
    accounts_pep_foreign: safeSub(
      events.accounts_pep_foreign_incr,
      events.accounts_pep_foreign_decr,
    ),

    // Number of other additional (other than PEPs and foreign PEPs) high-risk business relationships in 20XX (self-declaration 4.4.)
    accounts_high_risk_other: safeSub(
      safeSub(events.accounts_high_risk_incr, events.accounts_high_risk_decr), // 4.5
      safeSub(events.accounts_pep_incr, events.accounts_pep_decr), // 4.2
    ),

    // Number of high-risk business relationship n total in 20xx (self-declaration 4.5.)
    // comment: already implemented on 4.1

    // Number of reports (substantiated suspicion) to MROS during 20xx (self-declaration 5.1)
    mros_suspicion_substantiated: events.mros_suspicion_substantiated,

    // Number of reports (simple suspicion) to MROS during 20xx (self-declaration 5.2)
    mros_suspicion_simple: events.mros_suspicion_simple,

    // Total number of reports to MROS during 20xx (self-declaration 5.3)
    mros_total: safeAdd(
      events.mros_suspicion_substantiated,
      events.mros_suspicion_simple,
    ),
  };
};

export type CounterResultByEventName<T> = {
  [name in keyof T]?: number;
};

export type EventQueryByEventName<T> = {
  [name in keyof T]: EventQuery<string>;
};

function safeSub(
  a: number | undefined,
  b: number | undefined,
): number | undefined {
  return a === undefined || b == undefined ? undefined : a - b;
}
function safeAdd(
  a: number | undefined,
  b: number | undefined,
): number | undefined {
  return a === undefined || b == undefined ? undefined : a + b;
}

export async function fetchTopsInfoFromServer(
  api: TalerExchangeHttpClient,
  officer: OfficerAccount,
) {
  type EventType = typeof EventReporting_TOPS_queries;
  const eventList = Object.entries(EventReporting_TOPS_queries);

  const allQueries = eventList.map(async ([_key, value]) => {
    const key = _key as keyof EventType;
    const response = await api.getAmlKycStatistics(officer, [value.event], {
      since: value.start,
      until: value.end,
    });
    return { key, response };
  });

  const allResponses = await Promise.all(allQueries);

  const resultMap = allResponses.reduce((prev, event) => {
    prev[event.key] =
      event.response.type === "ok" ? event.response.body.statistics[0].counter : undefined;
    return prev;
  }, {} as CounterResultByEventName<EventType>);

  return EventReporting_TOPS_calculation(resultMap);
}

export async function fetchVqfInfoFromServer(
  api: TalerExchangeHttpClient,
  officer: OfficerAccount,
  jan_1st: AbsoluteTime,
  dec_31st: AbsoluteTime,
) {
  const VQF_EVENTS_THIS_YEAR = EventReporting_VQF_queries(jan_1st, dec_31st);
  type EventType = typeof VQF_EVENTS_THIS_YEAR;
  const eventList = Object.entries(VQF_EVENTS_THIS_YEAR);

  // type QueryAndEvent = { query: string; eventName: string };
  // type RawTime = AbsoluteTime["t_ms"];
  // // group by start & end
  // const groupedEvents = eventList.reduce(
  //   (prev, [query, metric]) => {
  //     if (!prev.has(metric.start.t_ms)) {
  //       prev.set(metric.start.t_ms, new Map<RawTime, QueryAndEvent[]>());
  //     }
  //     const st = prev.get(metric.start.t_ms)!;
  //     if (!st.has(metric.end.t_ms)) {
  //       st.set(metric.end.t_ms, []);
  //     }
  //     const ed = st.get(metric.end.t_ms)!;

  //     ed.push({
  //       query,
  //       eventName: metric.event,
  //     });
  //     return prev;
  //   },
  //   {} as Map<RawTime, Map<RawTime, QueryAndEvent[]>>,
  // );

  // groupedEvents.entries()
  // const groupedEventsList = Object.entries(groupedEvents).flatMap(
  //   ([startStr, map]) => {
  //     return Object.entries(map).flatMap(([endStr, list]) => {
  //       const start =
  //         startStr === "never"
  //           ? AbsoluteTime.never()
  //           : AbsoluteTime.fromMilliseconds(Number.parseInt(startStr, 10));
  //       const end =
  //         endStr === "never"
  //           ? AbsoluteTime.never()
  //           : AbsoluteTime.fromMilliseconds(Number.parseInt(endStr, 10));

  //       return list;
  //     });
  //   },
  // );

  const allQueries = eventList.map(async ([_key, value]) => {
    const key = _key as keyof EventType;
    const response = await api.getAmlKycStatistics(officer, [value.event], {
      since: value.start,
      until: value.end,
    });
    return { key, response };
  });

  const allResponses = await Promise.all(allQueries);

  const resultMap = allResponses.reduce((prev, event) => {
    prev[event.key] =
      event.response.type === "ok" ? event.response.body.statistics[0].counter : undefined;
    return prev;
  }, {} as CounterResultByEventName<EventType>);

  return EventReporting_VQF_calculation(resultMap);
}
