import { randomInt } from 'crypto';
import { EntityManager } from 'typeorm';
import { SYSTEM_BLOCK_KEYS_MAP, SYSTEM_BLOCK_KEYS_NUM } from '../constants';
import { AssetBlockEntity } from '../entities/asset-block.entity';
import {
  decodeBigNumberKey,
  encodeBigNumberKey,
} from '../utils/big-number-key';
import { objectCamelCase } from '../utils/types';
import {
  AssetBlockKeyStruct,
  normalizeAssetPropIdentifier,
} from './logic/Props';
import { AssetRights } from './logic/Rights';
import { AssetSelectorContext } from './asset-selector.service';
import { AssetForeignBlockEntity } from '../entities/asset-foreign-block.entity';

export class AssetBlock extends AssetBlockEntity {
  name: string | null;
  type: string;
  rights: AssetRights | null;
  filledRate: number;
  blockKeyTitle: string | null;
  // computedProps: AssetProps;
}
export async function registerAssetBlockKeyTx(
  tx: EntityManager,
  project_id: string,
  name: string | null,
  title: string,
  type: string,
): Promise<string> {
  const existen = await getAssetBlockKeyTx(tx, project_id, name, title, type);
  if (existen) return existen;

  name = normalizeAssetPropIdentifier(name);
  title = normalizeAssetPropIdentifier(title);
  type = normalizeAssetPropIdentifier(type);

  let key: string;
  if (name && SYSTEM_BLOCK_KEYS_MAP.hasOwnProperty(`${name}||${type}`)) {
    key = SYSTEM_BLOCK_KEYS_MAP[`${name}||${type}`].toString();
  } else {
    key = randomInt(SYSTEM_BLOCK_KEYS_NUM + 1, 140737488355328).toString();
  }
  const res = await tx.query(
    `
    INSERT INTO asset_block_keys(project_id, block_key, block_name, block_title, block_type)
    VALUES($1, $2, $3, $4, $5)
    ON CONFLICT (project_id, block_name, block_title, block_type) DO UPDATE SET block_name = excluded.block_name
    RETURNING block_key
  `,
    [
      decodeBigNumberKey(project_id),
      key,
      name ? name : '',
      !name ? title : '',
      type,
    ],
  );
  return res[0].block_key;
}

export async function getAssetBlockKeyTx(
  tx: EntityManager,
  project_id: string,
  name: string | null,
  title: string,
  type: string,
): Promise<string | undefined> {
  name = normalizeAssetPropIdentifier(name);
  title = normalizeAssetPropIdentifier(title);
  type = normalizeAssetPropIdentifier(type);

  const res = await tx
    .createQueryBuilder('asset_block_keys', 'bk')
    .where(
      `project_id = :project_id
      AND block_name = :name
      AND block_title = :title 
      AND block_type = :type`,
      {
        project_id: decodeBigNumberKey(project_id),
        name: name ? name : '',
        title: !name ? title : '',
        type,
      },
    )
    .select(['block_key'])
    .getRawOne();
  return res ? res.block_key : undefined;
}

function _getAssetBlocksQueryTx(
  tx: EntityManager,
  project_id: string,
  asset_ids: string[],
) {
  const dbquery = tx
    .createQueryBuilder(AssetBlockEntity, 'b')
    .innerJoin(
      'asset_block_keys',
      'bk',
      'b.block_key = bk.block_key AND bk.project_id = :project_id',
      {
        project_id: decodeBigNumberKey(project_id),
      },
    )
    .leftJoin(
      'asset_block_comps',
      'bc',
      'b.block_key = bc.block_key AND bc.project_id = b.project_id AND bc.asset_id = b.asset_id',
    )
    .where('b.asset_id = ANY(:asset_ids)', {
      asset_ids,
    })
    .select([
      'b.project_id as project_id',
      'b.block_key as block_key',
      'b.asset_id as asset_id',
      'b.title as title',
      'bk.block_title as block_key_title',
      'bk.block_name as name',
      'bk.block_type as type',
      'b.props as props',
      // 'bc.computed_props as computed_props',
      'b.created_at as created_at',
      'b.updated_at as updated_at',
      'b.index as index',
      'b.has_formula as has_formula',
      'COALESCE(bc.filled_rate, 0) as filled_rate',
    ]);
  return dbquery;
}

function _getAssetForeignBlocksQueryTx(
  tx: EntityManager,
  project_id: string,
  asset_ids: string[],
) {
  const dbquery = tx
    .createQueryBuilder(AssetForeignBlockEntity, 'b')
    .innerJoin(
      'asset_block_keys',
      'bk',
      'b.block_key = bk.block_key AND bk.project_id = :project_id',
      {
        project_id: decodeBigNumberKey(project_id),
      },
    )
    .innerJoin(
      'asset_blocks',
      'fb',
      'b.foreign_block_key = fb.block_key AND b.foreign_asset_id = fb.asset_id AND b.project_id = fb.project_id',
    )
    .leftJoin(
      'asset_block_comps',
      'bc',
      'fb.block_key = bc.block_key AND bc.project_id = fb.project_id AND bc.asset_id = fb.asset_id',
    )
    .where('b.asset_id = ANY(:asset_ids)', {
      asset_ids,
    })
    .select([
      'b.project_id as project_id',
      'b.block_key as block_key',
      'b.asset_id as asset_id',
      'COALESCE(b.title, fb.title) as title',
      'bk.block_title as block_key_title',
      'bk.block_name as name',
      'bk.block_type as type',
      'bc.computed_props || fb.props as props',
      'b.created_at as created_at',
      'b.updated_at as updated_at',
      'COALESCE(b.index, fb.index) as index',
      'fb.has_formula as has_formula',
      'COALESCE(bc.filled_rate, 0) as filled_rate',
    ]);
  return dbquery;
}

export async function getAssetAllBlocksTx(
  tx: EntityManager,
  asset_ids: string[],
  context: AssetSelectorContext,
): Promise<AssetBlock[]> {
  if (asset_ids.length === 0) {
    return [];
  }

  const dbquery = _getAssetBlocksQueryTx(tx, context.projectId, asset_ids);
  const res = await dbquery.getRawMany();

  const foreign_dbquery = _getAssetForeignBlocksQueryTx(
    tx,
    context.projectId,
    asset_ids,
  );
  const foreign_res = await foreign_dbquery.getRawMany();

  return [...res, ...foreign_res].map((r) => {
    const res = objectCamelCase<AssetBlock>(r);
    res.blockKeyTitle = res.blockKeyTitle ? res.blockKeyTitle : null;
    res.projectId = encodeBigNumberKey(res.projectId);
    res.rights = null;
    return res;
  });
}

export async function getAssetBlockKeysTx(
  tx: EntityManager,
  project_id: string,
  block_key_structures: AssetBlockKeyStruct[],
): Promise<any[]> {
  const result = [];
  await tx.transaction(async (tx) => {
    for (const bks of block_key_structures) {
      const name = normalizeAssetPropIdentifier(bks.blockName);
      const title = normalizeAssetPropIdentifier(bks.blockTitle);
      const type = normalizeAssetPropIdentifier(bks.blockType);

      const db_answer = await tx
        .createQueryBuilder('asset_block_keys', 'bk')
        .where(
          `project_id = :project_id
          AND block_name = :name
          AND block_title = :title 
          AND block_type = :type`,
          {
            project_id: decodeBigNumberKey(project_id),
            name: name ? name : '',
            title: !name ? title : '',
            type,
          },
        )
        .select(['block_key'])
        .getRawOne();
      if (db_answer) {
        result.push({
          ...bks,
          blockKey: db_answer.block_key,
        });
      }
    }
  });

  return result;
}
