import { ResultAsync, errAsync, okAsync } from "neverthrow";
import { FlowExecCtx } from "@core/flow.execution.context";
import { Database, asc, eq } from "@pkg/db";
import { supportedApp } from "@pkg/db/schema";
import { type Err } from "@pkg/result";
import { logger } from "@pkg/logger";
import { traceResultAsync } from "@core/observability";
import {
    CreateSupportedApp,
    SupportedApp,
    UpdateSupportedApp,
} from "./data";
import { supportedAppErrors } from "./errors";

export class SupportedAppRepository {
    constructor(private db: Database) {}

    list(fctx: FlowExecCtx): ResultAsync<SupportedApp[], Err> {
        return traceResultAsync({
            name: "supportedApp.list",
            fctx,
            fn: () =>
                ResultAsync.fromPromise(
                    this.db
                        .select()
                        .from(supportedApp)
                        .orderBy(asc(supportedApp.createdAt)),
                    (e) =>
                        supportedAppErrors.listFailed(
                            fctx,
                            e instanceof Error ? e.message : String(e),
                        ),
                ).map((rows) => rows as SupportedApp[]),
        });
    }

    getById(fctx: FlowExecCtx, id: number): ResultAsync<SupportedApp, Err> {
        return traceResultAsync({
            name: "supportedApp.getById",
            fctx,
            fn: () =>
                ResultAsync.fromPromise(
                    this.db.query.supportedApp.findFirst({
                        where: eq(supportedApp.id, id),
                    }),
                    (e) =>
                        supportedAppErrors.dbError(
                            fctx,
                            e instanceof Error ? e.message : String(e),
                        ),
                ).andThen((row) => {
                    if (!row) {
                        return errAsync(
                            supportedAppErrors.supportedAppNotFound(fctx, id),
                        );
                    }
                    return okAsync(row as SupportedApp);
                }),
        });
    }

    create(
        fctx: FlowExecCtx,
        data: CreateSupportedApp,
    ): ResultAsync<SupportedApp, Err> {
        logger.info("Creating supported app", { ...fctx, title: data.title });

        return traceResultAsync({
            name: "supportedApp.create",
            fctx,
            fn: () =>
                ResultAsync.fromPromise(
                    this.db
                        .insert(supportedApp)
                        .values({
                            title: data.title,
                            packageName: data.packageName,
                            createdAt: new Date(),
                            updatedAt: new Date(),
                        })
                        .returning()
                        .execute(),
                    (e) =>
                        supportedAppErrors.createFailed(
                            fctx,
                            e instanceof Error ? e.message : String(e),
                        ),
                ).map((rows) => rows[0] as SupportedApp),
        });
    }

    update(
        fctx: FlowExecCtx,
        id: number,
        updates: UpdateSupportedApp,
    ): ResultAsync<SupportedApp, Err> {
        return traceResultAsync({
            name: "supportedApp.update",
            fctx,
            fn: () =>
                this.getById(fctx, id).andThen(() =>
                    ResultAsync.fromPromise(
                        this.db
                            .update(supportedApp)
                            .set({ ...updates, updatedAt: new Date() })
                            .where(eq(supportedApp.id, id))
                            .returning()
                            .execute(),
                        (e) =>
                            supportedAppErrors.updateFailed(
                                fctx,
                                e instanceof Error ? e.message : String(e),
                            ),
                    ).andThen((rows) => {
                        if (!rows[0]) {
                            return errAsync(
                                supportedAppErrors.supportedAppNotFound(
                                    fctx,
                                    id,
                                ),
                            );
                        }
                        return okAsync(rows[0] as SupportedApp);
                    }),
                ),
        });
    }

    delete(fctx: FlowExecCtx, id: number): ResultAsync<boolean, Err> {
        return traceResultAsync({
            name: "supportedApp.delete",
            fctx,
            fn: () =>
                this.getById(fctx, id).andThen(() =>
                    ResultAsync.fromPromise(
                        this.db
                            .delete(supportedApp)
                            .where(eq(supportedApp.id, id))
                            .execute(),
                        (e) =>
                            supportedAppErrors.deleteFailed(
                                fctx,
                                e instanceof Error ? e.message : String(e),
                            ),
                    ).map(() => true),
                ),
        });
    }
}
