import { FlowExecCtx } from "@/core/flow.execution.context";
import { traceResultAsync } from "@core/observability";
import { ERROR_CODES, type Err } from "@pkg/result";
import { getError, logDomainEvent } from "@pkg/logger";
import { auth } from "../auth/config.base";
import { account } from "@pkg/db/schema";
import { ResultAsync, errAsync, okAsync } from "neverthrow";
import { Database, eq } from "@pkg/db";
import { nanoid } from "nanoid";

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

    private dbError(fctx: FlowExecCtx, detail: string): Err {
        return getError({
            flowId: fctx.flowId,
            code: ERROR_CODES.DATABASE_ERROR,
            message: "Database operation failed",
            description: "Please try again later",
            detail,
        });
    }

    private accountNotFound(fctx: FlowExecCtx): Err {
        return getError({
            flowId: fctx.flowId,
            code: ERROR_CODES.NOT_FOUND,
            message: "Account not found",
            description: "Please try again later",
            detail: "Account not found for user",
        });
    }

    ensureAccountExists(
        fctx: FlowExecCtx,
        userId: string,
    ): ResultAsync<boolean, Err> {
        return traceResultAsync({
            name: "logic.user.repository.ensureAccountExists",
            fctx,
            attributes: { "app.user.id": userId },
            fn: () => {
                const startedAt = Date.now();
                logDomainEvent({
                    event: "account.ensure_exists.started",
                    fctx,
                    meta: { userId },
                });

                return ResultAsync.fromPromise(
                    this.db.query.account.findFirst({
                        where: eq(account.userId, userId),
                    }),
                    (error) => {
                        logDomainEvent({
                            level: "error",
                            event: "account.ensure_exists.failed",
                            fctx,
                            durationMs: Date.now() - startedAt,
                            error,
                            meta: { userId },
                        });
                        return this.dbError(
                            fctx,
                            error instanceof Error ? error.message : String(error),
                        );
                    },
                ).andThen((existingAccount) => {
                    if (existingAccount) {
                        logDomainEvent({
                            event: "account.ensure_exists.succeeded",
                            fctx,
                            durationMs: Date.now() - startedAt,
                            meta: { userId, existed: true },
                        });
                        return okAsync(true);
                    }

                    return ResultAsync.fromPromise(
                        auth.$context.then((ctx) => ctx.password.hash(nanoid())),
                        (error) => {
                            logDomainEvent({
                                level: "error",
                                event: "account.ensure_exists.failed",
                                fctx,
                                durationMs: Date.now() - startedAt,
                                error,
                                meta: { userId, stage: "hash_password" },
                            });
                            return this.dbError(
                                fctx,
                                error instanceof Error
                                    ? error.message
                                    : String(error),
                            );
                        },
                    ).andThen((password) => {
                        const aid = nanoid();

                        return ResultAsync.fromPromise(
                            this.db
                                .insert(account)
                                .values({
                                    id: aid,
                                    accountId: userId,
                                    providerId: "credential",
                                    userId,
                                    password,
                                    createdAt: new Date(),
                                    updatedAt: new Date(),
                                })
                                .execute(),
                            (error) => {
                                logDomainEvent({
                                    level: "error",
                                    event: "account.ensure_exists.failed",
                                    fctx,
                                    durationMs: Date.now() - startedAt,
                                    error,
                                    meta: { userId, stage: "create_account" },
                                });
                                return this.dbError(
                                    fctx,
                                    error instanceof Error
                                        ? error.message
                                        : String(error),
                                );
                            },
                        ).map(() => {
                            logDomainEvent({
                                event: "account.ensure_exists.succeeded",
                                fctx,
                                durationMs: Date.now() - startedAt,
                                meta: { userId, existed: false },
                            });
                            return false;
                        });
                    });
                });
            },
        });
    }

    rotatePassword(
        fctx: FlowExecCtx,
        userId: string,
        password: string,
    ): ResultAsync<string, Err> {
        return traceResultAsync({
            name: "logic.user.repository.rotatePassword",
            fctx,
            attributes: { "app.user.id": userId },
            fn: () => {
                const startedAt = Date.now();
                logDomainEvent({
                    event: "account.rotate_password.started",
                    fctx,
                    meta: { userId },
                });

                return ResultAsync.fromPromise(
                    this.db.query.account.findFirst({
                        where: eq(account.userId, userId),
                    }),
                    (error) => {
                        logDomainEvent({
                            level: "error",
                            event: "account.rotate_password.failed",
                            fctx,
                            durationMs: Date.now() - startedAt,
                            error,
                            meta: { userId, stage: "check_exists" },
                        });
                        return this.dbError(
                            fctx,
                            error instanceof Error
                                ? error.message
                                : String(error),
                        );
                    },
                ).andThen((existingAccount) => {
                    if (!existingAccount) {
                        logDomainEvent({
                            level: "warn",
                            event: "account.rotate_password.failed",
                            fctx,
                            durationMs: Date.now() - startedAt,
                            error: { code: "NOT_FOUND", message: "Account not found" },
                            meta: { userId },
                        });
                        return errAsync(this.accountNotFound(fctx));
                    }

                    return ResultAsync.fromPromise(
                        auth.$context.then((ctx) => ctx.password.hash(password)),
                        (error) => {
                            logDomainEvent({
                                level: "error",
                                event: "account.rotate_password.failed",
                                fctx,
                                durationMs: Date.now() - startedAt,
                                error,
                                meta: { userId, stage: "hash_password" },
                            });
                            return this.dbError(
                                fctx,
                                error instanceof Error
                                    ? error.message
                                    : String(error),
                            );
                        },
                    ).andThen((hashed) => {
                        return ResultAsync.fromPromise(
                            this.db
                                .update(account)
                                .set({ password: hashed })
                                .where(eq(account.userId, userId))
                                .returning()
                                .execute(),
                            (error) => {
                                logDomainEvent({
                                    level: "error",
                                    event: "account.rotate_password.failed",
                                    fctx,
                                    durationMs: Date.now() - startedAt,
                                    error,
                                    meta: { userId, stage: "update_password" },
                                });
                                return this.dbError(
                                    fctx,
                                    error instanceof Error
                                        ? error.message
                                        : String(error),
                                );
                            },
                        ).map(() => {
                            logDomainEvent({
                                event: "account.rotate_password.succeeded",
                                fctx,
                                durationMs: Date.now() - startedAt,
                                meta: { userId },
                            });
                            return password;
                        });
                    });
                });
            },
        });
    }
}
