import { StandardsDataSchema, StandardsParamsSchema, type StandardsConfig, type StandardsData, type StandardsParams } from "@blade-and-brawn/calculator";
import { db } from "../database/db";
import { Value } from "@sinclair/typebox/value";

export class StandardsService {
    Configs: StandardsConfigService;
    Datasets: StandardsDatasetService;

    constructor() {
        this.Configs = new StandardsConfigService();
        this.Datasets = new StandardsDatasetService();
    }
}

class StandardsConfigService {
    async create(name: string, datasetId: string, params: StandardsParams): Promise<{ id: string }> {
        return await db.insertInto("standards_configs")
            .values({ name, params, dataset_id: datasetId })
            .returning("id")
            .executeTakeFirstOrThrow();
    }

    async list(): Promise<{ id: string, name: string, datasetId: string }[]> {
        return await db.selectFrom("standards_configs")
            .select([
                "id",
                "name",
                "dataset_id as datasetId",
            ])
            .orderBy("name")
            .execute();
    }

    async get(id: string): Promise<StandardsConfig & { id: string, name: string, datasetId: string }> {
        const result = await db.selectFrom("standards_configs")
            .innerJoin("standards_datasets", "standards_datasets.id", "standards_configs.dataset_id")
            .select([
                "standards_configs.id as id",
                "standards_configs.name as name",
                "standards_configs.dataset_id as datasetId",
                "standards_configs.params as params",
                "standards_datasets.data as data",
            ])
            .where("standards_configs.id", "=", id)
            .executeTakeFirstOrThrow({ errorConstructor: () => new Error(`No config with id "${id}"`) });
        Value.Assert(StandardsParamsSchema, result.params);
        Value.Assert(StandardsDataSchema, result.data);
        return {
            id: result.id,
            name: result.name,
            datasetId: result.datasetId,
            params: result.params,
            data: result.data,
        };
    }

    async update(id: string, name: string, datasetId: string, params: StandardsParams) {
        const result = await db.updateTable("standards_configs")
            .set({ name, params, dataset_id: datasetId })
            .where("id", "=", id)
            .execute();
        if (result[0]?.numUpdatedRows === 0n) throw new Error(`No config with id "${id}"`);
    }
}

class StandardsDatasetService {
    async list(): Promise<{ id: string, name: string }[]> {
        return await db.selectFrom("standards_datasets")
            .select(["id", "name"])
            .orderBy("name")
            .execute();
    }

    async get(id: string): Promise<{ id: string, name: string, data: StandardsData }> {
        const result = await db.selectFrom("standards_datasets")
            .select(["id", "name", "data"])
            .where("id", "=", id)
            .executeTakeFirstOrThrow({ errorConstructor: () => new Error(`No dataset with id "${id}"`) });
        Value.Assert(StandardsDataSchema, result.data);
        return { id: result.id, name: result.name, data: result.data };
    }
}
