import { Injectable, Logger } from '@nestjs/common';
import { Dashboards as DashboardsDb } from '../couchdb-datasources/dashboards';
import * as DashboardModel from '../models/dashboard';
import nano from 'nano';
import { randomUUID } from 'crypto';
import { createAppError } from '../utils/result';
import { TimestampNowFill } from '../utils/timestamp-now-fill';

@Injectable()
export class DashboardsService {
  private logger = new Logger(DashboardsService.name);

  constructor(private db: DashboardsDb) {}

  async create(): Promise<DashboardModel.Dashboard> {
    const id = randomUUID();
    this.logger.debug(`Create new dashboard with id - ${id}`);
    if (await this.isExists(id)) {
      const err = createAppError('ALREADY_EXISTS');
      this.logger.error(`Error - ${JSON.stringify(err)}`);
      throw err;
    }
    const ds = await this.db.getDatasource();
    const doc: nano.MaybeDocument & DashboardModel.Dashboard = {
      _id: id,
      id: id,
      data: null,
    };
    await ds.insert(doc);
    return await ds.get(id);
  }

  async loadRawData(
    id: string,
  ): Promise<DashboardModel.Dashboard & nano.MaybeDocument> {
    this.logger.debug(`Load raw data, dashboard id - ${id}`);
    const ds = await this.db.getDatasource();
    if (!(await this.isExists(id))) throw createAppError('NOT_EXISTS');
    const res = await ds.get(id);
    return res;
  }

  async load(id: string): Promise<DashboardModel.Data> {
    this.logger.debug(`Load dashboard id - ${id}`);
    const rawData = await this.loadRawData(id);
    return rawData?.data || { widgets: [] };
  }

  async isExists(id: string): Promise<boolean> {
    const ds = await this.db.getDatasource();
    try {
      await ds.get(id);
      return true;
    } catch (ex) {
      return false;
    }
  }

  async save(id: string, data: DashboardModel.Data): Promise<void> {
    this.logger.log(
      `Save dashboard id - ${id}, title - ${JSON.stringify(data.title)}`,
    );
    const ds = await this.db.getDatasource();
    const prevValue = await this.loadRawData(id);

    const newValue = TimestampNowFill({
      _id: prevValue._id,
      _rev: prevValue._rev,
      id: prevValue.id,
      data: data,
    });
    await ds.insert(newValue);
    return;
  }

  async publicList(limit: number): Promise<{ id: string; title: string }[]> {
    const ds = await this.db.getDatasource();
    const data = await ds.find({
      selector: {
        'data.title': {
          $exists: true,
        },
      },
      fields: ['id', 'data.title'],
      limit: limit,
    });
    if (!data.docs) throw createAppError('DASHBOARDS_NOT_FOUND');
    return data.docs.map((d) => ({ id: d.id, title: d.data.title }));
  }

  async findDashboardsByWidgetType(
    widgetType: string,
    updatedAfter?: number,
  ): Promise<DashboardModel.Dashboard[]> {
    const ds = await this.db.getDatasource();
    const selector = {
      'data.widgets': {
        $elemMatch: {
          type: widgetType,
        },
      },
    };
    if (updatedAfter > 0) {
      selector['timestamp__'] = {
        $gte: updatedAfter,
      };
    }
    this.logger.debug(
      `Find dashboards by widget type - ${widgetType} ` +
        `and selector - ${JSON.stringify(selector)}`,
    );
    const data = await ds.find({
      selector: selector,
    });
    this.logger.debug(
      `Found dashboards by widget type - ${widgetType} ` +
        `, selector - ${JSON.stringify(selector)}` +
        `, result - ${JSON.stringify(data)}`,
    );
    if (!data.docs) throw createAppError('DASHBOARDS_NOT_FOUND');
    return data.docs;
  }
}
