import { sql, type Kysely } from 'kysely';

import { inject, type Dependencies } from '../../iocContainer/index.js';
import { type RuleEnvironment } from '../../rule_engine/RuleEngine.js';
import {
  warehouseDateToDate,
  warehouseDateToDateOnlyString,
} from '../../storage/dataWarehouse/warehouseSchema.js';
import { jsonParse } from '../../utils/encoding.js';
import { getUtcDateOnlyString, WEEK_MS } from '../../utils/time.js';

type ItemHistoryQueryFilter = {
  passed?: boolean;
  environment?: RuleEnvironment;
  startDate?: Date;
  endDate?: Date;
};

class ItemHistoryQueries {
  constructor(private readonly dialect: Dependencies['DataWarehouseDialect']) {}

  // The data warehouse schema isn't statically modeled in TS, so we use
  // "any" here to opt out of Kysely's column-name checking. Kysely's stricter
  // alternatives ("Record<string, unknown>") reject the string-based column
  // selections this query relies on.
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see comment above
  private get kysely(): Kysely<any> {
    return this.dialect.getKyselyInstance();
  }

  async getItemRuleExecutionsHistory(opts: {
    itemId: string;
    itemTypeId: string | undefined;
    orgId: string;
    filters?: ItemHistoryQueryFilter;
  }) {
    const { itemId, itemTypeId, orgId, filters } = opts;
    const {
      passed,
      environment,
      startDate = new Date(Date.now() - WEEK_MS),
      endDate = new Date(Date.now()),
    } = filters ?? {};

    const query = this.kysely
      .selectFrom(sql`analytics.RULE_EXECUTIONS`.as('rule_exec'))
      .select([
        'ds',
        'ts',
        'item_type_name as itemTypeName',
        'item_type_id as itemTypeId',
        'item_creator_id as userId',
        'item_creator_type_id as userTypeId',
        'item_data as content',
        'result as result',
        'environment as environment',
        'passed as passed',
        'rule_id as ruleId',
        'rule as ruleName',
        'policy_names as policies',
        'tags as tags',
      ])
      .where('org_id', '=', orgId)
      .where('ds', '>=', getUtcDateOnlyString(startDate))
      .where('result', 'is not', null)
      .where('item_data', 'is not', null)
      .where(({ and, eb, fn, val }) => {
        return and([
          eb(fn('LOWER', ['item_id']), '=', fn('LOWER', [val(itemId)])),
          eb(
            fn('LOWER', ['item_type_id']),
            '=',
            fn('LOWER', [val(itemTypeId)]),
          ),
          eb('ds', '<=', getUtcDateOnlyString(endDate)),
          ...(environment ? [eb('environment', '=', environment)] : []),
          ...(passed != null ? [eb('passed', '=', passed)] : []),
        ]);
      });

    try {
      const results = await query.execute();
      return results.map((it) => ({
        itemTypeName: it.itemTypeName,
        itemTypeId: it.itemTypeId,
        userId: it.userId,
        userTypeId: it.userTypeId,
        content: it.content,
        result: jsonParse(it.result),
        environment: it.environment,
        passed: it.passed,
        ruleId: it.ruleId,
        ruleName: it.ruleName,
        policies: it.policies,
        tags: it.tags,
        date: warehouseDateToDateOnlyString(it.ds),
        ts: warehouseDateToDate(it.ts),
        contentId: itemId,
      }));
    } catch (error) {
      // eslint-disable-next-line no-console
      console.error(
        'ItemHistoryQueries: getItemRuleExecutionsHistory failed:',
        (error as Error).message,
      );
      throw error;
    }
  }
}

export default inject(['DataWarehouseDialect'], ItemHistoryQueries);
export { type ItemHistoryQueries };
