import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm';
import { AssetEntity } from '../entities/asset.entity';
import {
  AccessTag,
  ProjectAccessParams,
  ProjectRole,
} from '../utils/project-access';
import {
  AssetPropsSelection,
  AssetPropsSelectionBase,
} from './logic/PropsSelection';
import { ApiResultListWithTotal } from '../common/types/api-result';
import { AssetViewDTO } from './dto/asset-dto';
import { AssetPropValue } from './logic/Props';
import { AssetSelectorQuery } from './logic/AssetSelectorQuery';
import { ProjectLicense } from './logic/ProjectLicense';

export type AssetShortView = {
  [key: string]: AssetPropValue;
  id: string;
  name: string | null;
  title: string | null;
  icon: string | null;
  workspaceid: string | null;
  isabstract: boolean;
  typeids: number[];
  parentids: number[];
  createdat: string;
  updatedat: string;
  deletedat: string | null;
  creatoruserid: number | null;
  scope: string;
  rights: number;
  readinessrate: number | null;
  owntitle: string | null;
  ownicon: string | null;
  index: number | null;
};

export type AssetShort = {
  id: string;
  name: string | null;
  title: string | null;
  workspaceId: string | null;
  icon: string | null;
  isAbstract: boolean;
  typeIds: string[];
  parentIds: string[];
  createdAt: string;
  updatedAt: string;
  deletedAt: string | null;
  creatorUserId: number | null;
  scope: string;
  rights: number;
  readinessRate: number | null;
  ownTitle: string | null;
  ownIcon: string | null;
  index: number | null;
};

const ASSET_SHORT_VIEW_FIELDS = [
  'id',
  'name',
  'title',
  'workspaceid',
  'icon',
  'isabstract',
  'parentids',
  'typeids',
  'createdat',
  'updatedat',
  'deletedat',
  'creatoruserid',
  'scope',
  'rights',
  'readinessrate',
  'owntitle',
  'ownicon',
  'index',
];

export type AssetSelectorContext = {
  projectId: string;
  projectParams: ProjectAccessParams;
  userId?: number;
  userRole?: ProjectRole;
  tx?: EntityManager;
  accessTags: Set<AccessTag>;
  projectLicense?: ProjectLicense;
};

@Injectable()
export class AssetSelectorService {
  constructor(
    @InjectRepository(AssetEntity)
    private readonly assetRepo: Repository<AssetEntity>,
  ) {}

  async getShort(
    assetId: string,
    context: AssetSelectorContext,
  ): Promise<AssetShort | null> {
    const shorts = await this.getShorts(
      {
        where: {
          id: [assetId],
        },
        count: 1,
      },
      context,
    );
    return shorts.length > 0 ? shorts[0] : null;
  }

  async getShorts(
    selection: AssetPropsSelectionBase,
    context: AssetSelectorContext,
  ): Promise<AssetShort[]> {
    return (
      await this.getView<AssetShortView>(
        {
          ...selection,
          select: ASSET_SHORT_VIEW_FIELDS,
        },
        context,
      )
    ).map((av) => this._convertAssetShortFromView(av));
  }

  private _convertAssetShortFromView(av: AssetShortView): AssetShort {
    return {
      id: av.id,
      createdAt: av.createdat,
      creatorUserId: av.creatoruserid ? av.creatoruserid : null,
      deletedAt: av.deletedat,
      icon: av.icon,
      isAbstract: av.isabstract,
      name: av.name,
      typeIds: av.typeids.map((p) => {
        return av[`typeids\\${p}`] as string;
      }),
      parentIds: av.parentids.map((p) => {
        return av[`parentids\\${p}`] as string;
      }),
      title: av.title,
      updatedAt: av.updatedat,
      workspaceId: av.workspaceid,
      scope: av.scope.toString(),
      rights: av.rights,
      readinessRate: av.readinessrate,
      ownIcon: av.ownicon,
      ownTitle: av.owntitle,
      index: av.index,
    };
  }

  async getShortsMap(
    selection: AssetPropsSelectionBase,
    context: AssetSelectorContext,
  ): Promise<Map<string, AssetShort>> {
    const shorts = await this.getShorts(selection, context);
    const map = new Map<string, AssetShort>();
    for (const short of shorts) {
      map.set(short.id, short);
    }
    return map;
  }

  async getShortsPaginated(
    selection: AssetPropsSelectionBase,
    context: AssetSelectorContext,
  ): Promise<ApiResultListWithTotal<AssetShort>> {
    const res = await this.getViewPaginated<AssetShortView>(
      {
        ...selection,
        select: ASSET_SHORT_VIEW_FIELDS,
      },
      context,
    );
    return {
      list: res.list.map((r) => this._convertAssetShortFromView(r)),
      total: res.total,
    };
  }

  async getView<T extends AssetViewDTO>(
    selection: AssetPropsSelection,
    context: AssetSelectorContext,
  ): Promise<T[]> {
    if (
      (!selection.select || selection.select.length === 0) &&
      (!selection.group || selection.group.length === 0)
    ) {
      return [];
    }
    const query = new AssetSelectorQuery({
      ...context,
      tx: context.tx ? context.tx : this.assetRepo.manager,
    });
    if (selection.where !== undefined) query.where(selection.where);
    if (selection.group !== undefined) query.group(selection.group);
    if (selection.order !== undefined) query.order(selection.order);
    if (selection.count !== undefined) query.count(selection.count);
    if (selection.offset !== undefined) query.offset(selection.offset);
    if (selection.select !== undefined) query.select(selection.select);
    return await query.fetchRows<T>();
  }

  async getViewPaginated<T extends AssetViewDTO>(
    selection: AssetPropsSelection,
    context: AssetSelectorContext,
  ): Promise<ApiResultListWithTotal<T>> {
    const query = new AssetSelectorQuery({
      ...context,
      tx: context.tx ? context.tx : this.assetRepo.manager,
    });
    if (selection.where !== undefined) query.where(selection.where);
    if (selection.group !== undefined) query.group(selection.group);
    if (selection.select !== undefined) query.select(selection.select);
    const total = await query.fetchCount();

    if (
      (!selection.select || selection.select.length === 0) &&
      (!selection.group || selection.group.length === 0)
    ) {
      return {
        list: [],
        total,
      };
    }

    if (selection.order !== undefined) query.order(selection.order);
    if (selection.count !== undefined) query.count(selection.count);
    if (selection.offset !== undefined) query.offset(selection.offset);
    const list = await query.fetchRows<T>();
    return {
      list,
      total,
    };
  }

  async getIds(
    selection: AssetPropsSelectionBase,
    context: AssetSelectorContext,
  ): Promise<string[]> {
    const query = new AssetSelectorQuery({
      ...context,
      tx: context.tx ? context.tx : this.assetRepo.manager,
    });
    if (selection.where !== undefined) query.where(selection.where);
    if (selection.order !== undefined) query.order(selection.order);
    if (selection.count !== undefined) query.count(selection.count);
    if (selection.offset !== undefined) query.offset(selection.offset);
    query.select(['id']);
    return (await query.fetchRows<{ id: string }>()).map((r) => r.id);
  }

  async getIdsPaginated<T>(
    selection: AssetPropsSelectionBase,
    context: AssetSelectorContext,
  ): Promise<ApiResultListWithTotal<string>> {
    const query = new AssetSelectorQuery({
      ...context,
      tx: context.tx ? context.tx : this.assetRepo.manager,
    });
    if (selection.where !== undefined) query.where(selection.where);
    const total = await query.fetchCount();

    if (selection.order !== undefined) query.order(selection.order);
    if (selection.count !== undefined) query.count(selection.count);
    if (selection.offset !== undefined) query.offset(selection.offset);

    query.select(['id']);
    const list = (await query.fetchRows<{ id: string }>()).map((r) => r.id);

    return {
      list,
      total,
    };
  }
}
