/*
 * Copyright (C) 2019, 2020 StApps
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
import {SCConfigFile, SCSearchQuery, SCSearchResponse, SCThings, SCThingType, SCUuid} from '@openstapps/core';
import {Express} from 'express';
import moment from 'moment';
import {configureApp} from '../src/app.js';
import express from 'express';
import http from 'http';
import {MailQueue} from '../src/notification/mail-queue.js';
import {Bulk, BulkStorage} from '../src/storage/bulk-storage.js';
import getPort from 'get-port';
import {Database, SupplementaryGeoJSON} from '../src/storage/database.js';
import {v4} from 'uuid';
import {backendConfig} from '../src/config.js';
import {getIndexUID} from '../src/storage/elasticsearch/util/index.js';

/**
 * Adds routers and configures an (express) app
 *
 */
export async function startApp(): Promise<Express> {
  const app = express();

  await configureApp(app, {elasticsearch: ElasticsearchMock});

  const server = http.createServer(app);

  // get a random free port
  const port = await getPort();
  server.listen(port);

  server.on('error', error => {
    throw error;
  });

  return new Promise(resolve =>
    server.on('listening', () => {
      app.set('bulk', bulkStorageMock);
      resolve(app);
    }),
  );
}

/**
 * An elasticsearch mock
 */
export class ElasticsearchMock implements Database {
  private bulk: Bulk | undefined;

  private storageMock = new Map<string, SCThings>();

  constructor(_configFile: SCConfigFile, _mailQueue?: MailQueue) {
    // Nothing to do here
  }

  geo(): Promise<SupplementaryGeoJSON> {
    throw new Error('Method not implemented.');
  }

  bulkCreated(bulk: Bulk): Promise<void> {
    this.bulk = bulk;
    return Promise.resolve(undefined);
  }

  bulkExpired(_bulk: Bulk): Promise<void> {
    return Promise.resolve(undefined);
  }

  bulkUpdated(_bulk: Bulk): Promise<void> {
    return Promise.resolve(undefined);
  }

  get(uid: SCUuid): Promise<SCThings> {
    // @ts-expect-error incompatible types
    return Promise.resolve(this.storageMock.get(uid));
  }

  init(): Promise<void> {
    return Promise.resolve();
  }

  post(_thing: SCThings, _bulk: Bulk): Promise<void> {
    return Promise.resolve();
  }

  put(thing: SCThings): Promise<void> {
    this.storageMock.set(thing.uid, thing);
    return Promise.resolve();
  }

  search(_parameters: SCSearchQuery): Promise<SCSearchResponse> {
    return Promise.resolve({
      data: [],
      facets: [],
      pagination: {count: 0, offset: 0, total: 0},
      stats: {time: 0},
    });
  }
}

export const bulkStorageMock = new BulkStorage(new ElasticsearchMock(backendConfig));

export const bulk: Bulk = {
  expiration: moment().add(3600, 'seconds').format(),
  source: 'some_source',
  state: 'in progress',
  type: SCThingType.Book,
  uid: '',
};

export class FooError extends Error {}

export const DEFAULT_TEST_TIMEOUT = 10_000;

export const TRANSPORT_SEND_RESPONSE = 'Send Response';

export const getTransport = (verified: boolean) => {
  return {
    cc: undefined,
    from: undefined,
    recipients: undefined,
    transportAgent: undefined,
    verified: undefined,
    isVerified(): boolean {
      return verified;
    },
    send(_subject: string, _message: string): Promise<string> {
      return Promise.resolve('');
    },
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    sendMail(_mail: any): Promise<string> {
      return Promise.resolve(TRANSPORT_SEND_RESPONSE);
    },
    verify(): Promise<boolean> {
      return Promise.resolve(false);
    },
  };
};

export const getIndex = (uid?: string) => `stapps_footype_foosource_${uid ?? getIndexUID(v4())}`;
