src/services/asset.type.systemtype.mappings.service.tsadded
import { LoggerFactory } from "../util/logger";
import { query } from "../db/db";
import { DB_ConstraintViolationError, NotFoundError, ResourceConflictError } from "../types/errortypes";
import { getLoggedInUsername } from "../util/username";

const logger = LoggerFactory("AssetTypeSystemTypeMappingsService");

const SQL = {
    LIST: `SELECT * FROM xyz."fn_GetAssetTypeSystemTypeMappingList"($1)`,
    GET_BY_ID: `SELECT * FROM xyz."fn_GetAssetTypeSystemTypeMapping"($1, $2)`,
    INSERT: `SELECT * FROM xyz."fn_InsertAssetTypeSystemTypeMapping"($1, $2, $3, $4)`,
    DELETE: `CALL xyz."usp_DeleteAssetTypeSystemTypeMapping"($1, $2)`,
};

export interface AssetTypeSystemTypeMapping {
    mappingId: string;
    assetTypeId: string;
    systemTypeId: string;
    createdBy: string;
    insertedOn: Date;
    lastModifiedOn: Date | null;
    lastModifiedBy: string | null;
}

function mapRow(row: any): AssetTypeSystemTypeMapping {
    return {
        mappingId: row.AssetTypeSystemTypeMappingId,
        assetTypeId: row.AssetTypeId,
        systemTypeId: row.SystemTypeId,
        createdBy: row.CreatedBy,
        insertedOn: row.InsertedOn,
        lastModifiedOn: row.LastModifiedOn ?? null,
        lastModifiedBy: row.LastModifiedBy ?? null,
    };
}

function mapError(err: unknown): never {
    if (err instanceof Error && (err.message.includes("AssetType with id") || err.message.includes("SystemType with id"))) {
        throw new NotFoundError(err.message);
    }
    if (err instanceof DB_ConstraintViolationError && err.message.includes("AssetTypeSystemTypeMapping")) {
        throw new ResourceConflictError("A system type mapping already exists for this asset type.");
    }
    throw err;
}

export async function listMappings(projectId: string): Promise<AssetTypeSystemTypeMapping[]> {
    try {
        const { rows } = await query(SQL.LIST, [projectId]);
        return rows.map(mapRow);
    } catch (err) {
        logger.error("Error while listing asset type system type mappings", err);
        throw err;
    }
}

export async function getMappingById(projectId: string, mappingId: string): Promise<AssetTypeSystemTypeMapping> {
    try {
        const { rows } = await query(SQL.GET_BY_ID, [projectId, mappingId]);
        if (rows.length === 0) {
            throw new NotFoundError(`AssetTypeSystemTypeMapping with id: ${mappingId} not found.`);
        }
        return mapRow(rows[0]);
    } catch (err) {
        logger.error("Error while fetching asset type system type mapping", err);
        throw err;
    }
}

export async function createMapping(projectId: string, assetTypeId: string, systemTypeId: string): Promise<AssetTypeSystemTypeMapping> {
    try {
        const { rows } = await query(SQL.INSERT, [projectId, assetTypeId, systemTypeId, getLoggedInUsername()]);
        return mapRow(rows[0]);
    } catch (err) {
        logger.error("Error while creating asset type system type mapping", err);
        mapError(err);
    }
}

export async function deleteMapping(projectId: string, mappingId: string): Promise<void> {
    try {
        await getMappingById(projectId, mappingId);
        await query(SQL.DELETE, [projectId, mappingId]);
    } catch (err) {
        logger.error("Error while deleting asset type system type mapping", err);
        throw err;
    }
}

/**
 * Returns a lookup of assetTypeId -> systemTypeId for every AssetType->SystemType
 * mapping in the project. Used to derive the scalar `systemId` on assets without
 * fetching a nested SystemType. Absent asset types simply have no entry.
 */
export async function getAssetTypeToSystemTypeMap(projectId: string): Promise<Map<string, string>> {
    const mappings = await listMappings(projectId);
    return new Map(mappings.map((m) => [m.assetTypeId, m.systemTypeId]));
}