import { getPayload } from "payload";
import type { SanitizedConfig } from "payload";
import {
  NoopTracer,
  NoopLogger,
  type ITracer,
  type ILogger,
} from "@repo/core-shared/instrumentation";
import type { IUsersRepository } from "../../application/repositories/users.repository.interface";
import { type User } from "../../entities/models/user";

const FEATURE = "auth" as const;
const REPO = "users" as const;

export class UsersRepository implements IUsersRepository {
  private config: SanitizedConfig;
  private tracer: ITracer;
  private logger: ILogger;

  constructor(
    config: SanitizedConfig,
    tracer: ITracer = new NoopTracer(),
    logger: ILogger = new NoopLogger(),
  ) {
    this.config = config;
    this.tracer = tracer;
    this.logger = logger;
  }

  async getUser(id: string): Promise<User | undefined> {
    return this.tracer.startSpan(
      { name: "users.getUser", op: "repository", attributes: { id } },
      async (span) => {
        try {
          const payload = await getPayload({ config: this.config });
          const result = await payload.findByID({
            collection: "users",
            id,
            overrideAccess: true,
          });
          const found = Boolean(result);
          span.setAttribute("found", found);
          return result ? this.toDomain(result as Record<string, unknown>) : undefined;
        } catch (err) {
          if (
            err &&
            typeof err === "object" &&
            "status" in err &&
            (err as { status: unknown }).status === 404
          ) {
            span.setAttribute("found", false);
            return undefined;
          }
          this.logger.captureException(err, {
            tags: { feature: FEATURE, repo: REPO, method: "getUser" },
          });
          span.setStatus("error", err instanceof Error ? err.message : String(err));
          throw err;
        }
      },
    );
  }

  async getUserByUsername(username: string): Promise<User | undefined> {
    return this.tracer.startSpan(
      {
        name: "users.getUserByUsername",
        op: "repository",
        attributes: { emailDomain: username.includes("@") ? (username.split("@")[1] ?? "(invalid)") : username },
      },
      async (span) => {
        try {
          const payload = await getPayload({ config: this.config });
          const { docs } = await payload.find({
            collection: "users",
            where: { username: { equals: username } },
            limit: 1,
            overrideAccess: true,
          });
          const doc = docs[0];
          span.setAttribute("found", Boolean(doc));
          return doc ? this.toDomain(doc as Record<string, unknown>) : undefined;
        } catch (err) {
          this.logger.captureException(err, {
            tags: { feature: FEATURE, repo: REPO, method: "getUserByUsername" },
          });
          span.setStatus("error", err instanceof Error ? err.message : String(err));
          throw err;
        }
      },
    );
  }

  async createUser(input: User): Promise<User> {
    return this.tracer.startSpan(
      { name: "users.createUser", op: "repository", attributes: { id: input.id } },
      async (span) => {
        try {
          const payload = await getPayload({ config: this.config });
          const created = await payload.create({
            collection: "users",
            data: {
              id: input.id,
              username: input.username,
              passwordHash: input.passwordHash,
            },
            overrideAccess: true,
          });
          span.setAttribute("created", true);
          return this.toDomain(created as Record<string, unknown>);
        } catch (err) {
          this.logger.captureException(err, {
            tags: { feature: FEATURE, repo: REPO, method: "createUser" },
          });
          span.setStatus("error", err instanceof Error ? err.message : String(err));
          throw err;
        }
      },
    );
  }

  private toDomain(doc: Record<string, unknown>): User {
    return {
      id: doc.id as string,
      username: doc.username as string,
      passwordHash: doc.passwordHash as string,
    };
  }
}
