import type { PrismaClient, Prisma, ResourceProposal } from '@prisma/client';

import type { ResourceType } from './resource-revision.repository.js';

/**
 * Generic propose/approve/reject queue keyed by (resourceType, name,
 * projectId|agentId). Successor to PromptRequest. The repo mirrors
 * PromptRepository's `?? ''` workaround for nullable-FK compound lookups.
 */

export type ProposalStatus = 'pending' | 'approved' | 'rejected';

export interface ProposalListFilter {
  resourceType?: ResourceType;
  projectId?: string;
  agentId?: string;
  status?: ProposalStatus;
}

export interface CreateProposalInput {
  resourceType: ResourceType;
  name: string;
  body: Prisma.InputJsonValue;
  projectId?: string;
  agentId?: string;
  createdBySession?: string;
  createdByUserId?: string;
}

export interface UpdateProposalStatusInput {
  status: ProposalStatus;
  reviewerNote?: string;
  approvedRevisionId?: string;
}

export interface IResourceProposalRepository {
  list(filter: ProposalListFilter): Promise<ResourceProposal[]>;
  findById(id: string): Promise<ResourceProposal | null>;
  findByName(resourceType: ResourceType, name: string, scope: { projectId: string | null; agentId: string | null }): Promise<ResourceProposal | null>;
  findBySession(sessionId: string, projectId?: string): Promise<ResourceProposal[]>;
  create(data: CreateProposalInput, tx?: Prisma.TransactionClient): Promise<ResourceProposal>;
  updateBody(id: string, body: Prisma.InputJsonValue): Promise<ResourceProposal>;
  updateStatus(id: string, data: UpdateProposalStatusInput, tx?: Prisma.TransactionClient): Promise<ResourceProposal>;
  delete(id: string): Promise<void>;
}

export class ResourceProposalRepository implements IResourceProposalRepository {
  constructor(private readonly prisma: PrismaClient) {}

  async list(filter: ProposalListFilter): Promise<ResourceProposal[]> {
    const where: Prisma.ResourceProposalWhereInput = {};
    if (filter.resourceType) where.resourceType = filter.resourceType;
    if (filter.status) where.status = filter.status;
    if (filter.projectId !== undefined) {
      // Match project-scoped + globals (NULL projectId), like PromptRepo.
      where.OR = [{ projectId: filter.projectId }, { projectId: null, agentId: null }];
    }
    if (filter.agentId !== undefined) {
      where.agentId = filter.agentId;
    }
    return this.prisma.resourceProposal.findMany({
      where,
      include: {
        project: { select: { name: true } },
        agent: { select: { name: true } },
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async findById(id: string): Promise<ResourceProposal | null> {
    return this.prisma.resourceProposal.findUnique({
      where: { id },
      include: {
        project: { select: { name: true } },
        agent: { select: { name: true } },
      },
    });
  }

  async findByName(
    resourceType: ResourceType,
    name: string,
    scope: { projectId: string | null; agentId: string | null },
  ): Promise<ResourceProposal | null> {
    if (scope.agentId !== null) {
      return this.prisma.resourceProposal.findUnique({
        where: { resourceType_name_agentId: { resourceType, name, agentId: scope.agentId } },
      });
    }
    // Project-scoped or global (projectId=null is handled by the same compound key).
    return this.prisma.resourceProposal.findUnique({
      where: { resourceType_name_projectId: { resourceType, name, projectId: scope.projectId ?? '' } },
    });
  }

  async findBySession(sessionId: string, projectId?: string): Promise<ResourceProposal[]> {
    const where: Prisma.ResourceProposalWhereInput = { createdBySession: sessionId };
    if (projectId !== undefined) {
      where.OR = [{ projectId }, { projectId: null, agentId: null }];
    }
    return this.prisma.resourceProposal.findMany({
      where,
      orderBy: { createdAt: 'desc' },
    });
  }

  async create(data: CreateProposalInput, tx?: Prisma.TransactionClient): Promise<ResourceProposal> {
    const client = tx ?? this.prisma;
    return client.resourceProposal.create({ data });
  }

  async updateBody(id: string, body: Prisma.InputJsonValue): Promise<ResourceProposal> {
    return this.prisma.resourceProposal.update({
      where: { id },
      data: { body, version: { increment: 1 } },
    });
  }

  async updateStatus(
    id: string,
    data: UpdateProposalStatusInput,
    tx?: Prisma.TransactionClient,
  ): Promise<ResourceProposal> {
    const client = tx ?? this.prisma;
    const update: Prisma.ResourceProposalUpdateInput = {
      status: data.status,
      version: { increment: 1 },
    };
    if (data.reviewerNote !== undefined) update.reviewerNote = data.reviewerNote;
    if (data.approvedRevisionId !== undefined) update.approvedRevisionId = data.approvedRevisionId;
    return client.resourceProposal.update({ where: { id }, data: update });
  }

  async delete(id: string): Promise<void> {
    await this.prisma.resourceProposal.delete({ where: { id } });
  }
}
