import { $inject, Alepha, z } from "alepha";
import { jobExecutionEntity } from "alepha/api/jobs";
import { $repository } from "alepha/orm";
import { $secure, currentTenantAtom } from "alepha/security";
import { $action, NotFoundError, okSchema } from "alepha/server";
import { NotificationJobs } from "../jobs/NotificationJobs.ts";
import { notificationDetailResourceSchema } from "../schemas/notificationDetailResourceSchema.ts";
import { notificationQuerySchema } from "../schemas/notificationQuerySchema.ts";
import { notificationResourceSchema } from "../schemas/notificationResourceSchema.ts";

export class AdminNotificationController {
  protected readonly url: string = "/notifications";
  protected readonly group: string = "admin:notifications";
  protected readonly alepha = $inject(Alepha);
  protected readonly notificationJobs = $inject(NotificationJobs);
  protected readonly executions = $repository(jobExecutionEntity);

  protected get jobName(): string {
    return this.notificationJobs.sendNotification.name;
  }

  /**
   * The tenant this request is acting in, when multi-tenant. The notification
   * outbox (`job_executions`) is shared across tenants in a pooled worker, so
   * every read/delete here is scoped to this org to prevent cross-tenant access.
   * Undefined in single-tenant apps → no extra filter (all rows are this app's).
   */
  protected get organizationId(): string | undefined {
    return this.alepha.store.get(currentTenantAtom)?.id;
  }

  /** True when `exec` belongs to the acting tenant (or the app is single-tenant). */
  protected sameTenant(exec: { organizationId?: string | null }): boolean {
    const org = this.organizationId;
    return !org || exec.organizationId === org;
  }

  public readonly findNotifications = $action({
    path: this.url,
    group: this.group,
    use: [$secure({ permissions: ["admin:notification:read"] })],
    schema: {
      query: notificationQuerySchema,
      response: z.page(notificationResourceSchema),
    },
    handler: async ({ query }) => {
      query.sort ??= "-createdAt";
      const where = this.executions.createQueryWhere();
      where.jobName = { eq: this.jobName };
      const org = this.organizationId;
      if (org) {
        where.organizationId = { eq: org };
      }
      const page = await this.executions.paginate(
        query,
        { where },
        { count: true },
      );
      return {
        ...page,
        content: page.content.map((exec) => this.toResource(exec)),
      } as any;
    },
  });

  public readonly getNotification = $action({
    path: `${this.url}/:id`,
    group: this.group,
    use: [$secure({ permissions: ["admin:notification:read"] })],
    schema: {
      params: z.object({
        id: z.uuid(),
      }),
      response: notificationDetailResourceSchema,
    },
    handler: async ({ params }) => {
      const exec = await this.executions.findById(params.id);
      if (!exec || exec.jobName !== this.jobName || !this.sameTenant(exec)) {
        throw new NotFoundError(`Notification not found: ${params.id}`);
      }
      return this.toDetailResource(exec) as any;
    },
  });

  public readonly deleteNotification = $action({
    method: "DELETE",
    path: `${this.url}/:id`,
    group: this.group,
    use: [$secure({ permissions: ["admin:notification:delete"] })],
    description: "Delete a notification record",
    schema: {
      params: z.object({
        id: z.uuid(),
      }),
      response: okSchema,
    },
    handler: async ({ params }) => {
      const exec = await this.executions.findById(params.id);
      if (!exec || exec.jobName !== this.jobName || !this.sameTenant(exec)) {
        throw new NotFoundError(`Notification not found: ${params.id}`);
      }
      await this.executions.deleteById(params.id);
      return { ok: true, id: params.id };
    },
  });

  public readonly deleteNotifications = $action({
    method: "POST",
    path: `${this.url}/delete`,
    group: this.group,
    use: [$secure({ permissions: ["admin:notification:delete"] })],
    description: "Delete many notification records in one call",
    schema: {
      body: z.object({
        ids: z.array(z.uuid()).min(1).max(1000),
      }),
      response: z.object({
        deleted: z.array(z.uuid()),
      }),
    },
    handler: async ({ body }) => {
      // Constrain to this job's executions so an admin can't delete arbitrary
      // job rows through this endpoint — and, when multi-tenant, to this org so
      // one club can't delete another club's notification records.
      const org = this.organizationId;
      const deleted = await this.executions.deleteMany({
        id: { inArray: body.ids },
        jobName: { eq: this.jobName },
        ...(org ? { organizationId: { eq: org } } : {}),
      });
      return { deleted: deleted.map(String) };
    },
  });

  protected toResource(exec: Record<string, unknown>) {
    const payload = (exec.payload ?? {}) as Record<string, unknown>;
    return {
      id: exec.id,
      createdAt: exec.createdAt,
      status: exec.status,
      template: payload.template,
      type: payload.type,
      contact: payload.contact,
      category: payload.category,
      critical: payload.critical,
      sensitive: payload.sensitive,
      startedAt: exec.startedAt,
      completedAt: exec.completedAt,
      error: exec.error,
    };
  }

  protected toDetailResource(exec: Record<string, unknown>) {
    const payload = (exec.payload ?? {}) as Record<string, unknown>;
    return {
      ...this.toResource(exec),
      variables: payload.variables,
      logs: exec.logs,
    };
  }
}
