import { Body, Controller, Post, Route, Security, SuccessResponse, Tags } from "tsoa";
import HttpStatusCode from "../interfaces/http-status";
import esClient from "../elasticsearch";
import { Search, SearchInfo, SearchOperator, SearchOptions } from "../interfaces/search";
import { StorageFile } from "../interfaces/storage-fs";
import { QueryDslQueryContainer } from "@elastic/elasticsearch/lib/api/types";
import HttpError from "../interfaces/http-error";

const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;

if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");

@Route("/search")
export class SearchController extends Controller {
  @Post("/")
  @Tags("Search")
  @Security("bearerAuth")
  @SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
  public async searchFile(
    @Body()
    body: {
      AND?: (
        | SearchInfo
        | {
            AND?: unknown;
            OR?: unknown;
          }
      )[];
      OR?: (
        | SearchInfo
        | {
            AND?: unknown;
            OR?: unknown;
          }
      )[];
    } & SearchOptions,
  ): Promise<StorageFile[]> {
    const search = body as Search;
    const type = ["match", "match_phrase"] as const;

    const searchMapCallback = (v: SearchInfo | SearchOperator): QueryDslQueryContainer => {
      if ("field" in v && "value" in v) {
        return { [type[search.exact || v.exact ? 1 : 0]]: { [v.field]: v.value } };
      }

      if ("AND" in v || "OR" in v) {
        return {
          bool: { must: v.AND?.map(searchMapCallback), should: v.OR?.map(searchMapCallback) },
        };
      }

      throw new HttpError(
        HttpStatusCode.UNPROCESSABLE_ENTITY,
        "ข้อมูลค้นหาไม่ถูกต้อง กรุณาตรวจสอบอีกครั้ง",
      );
    };

    const result = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
      index: DEFAULT_INDEX,
      query: {
        bool: {
          must: search.AND?.map(searchMapCallback),
          should: search.OR?.map(searchMapCallback),
          must_not: { match: { hidden: true } },
        },
      },
      post_filter: {
        [["match", "prefix"][search.recursive ? 1 : 0]]: {
          path: `${search.path?.join("/")}/`.replace(/^\//, "") || "",
        },
      },
      size: 10000,
    });

    return result.hits.hits.length > 0
      ? result.hits.hits
          .map((v) => {
            if (!v._source) return;

            const { attachment, ...rest } = v._source;

            return rest;
          })
          .flatMap((v) => (!!v ? [v] : []))
      : [];
  }
}
