import { Injectable } from '@nestjs/common';
import { and, eq, asc } from 'drizzle-orm';
import { z } from 'zod';
import { CreatePutawayRuleDto, UpdatePutawayRuleSchema, CreateAllocationRuleDto, UpdateAllocationRuleSchema, CreateReasonCodeSchema, UpdateReasonCodeSchema } from '@wms/shared';
import { DbService } from '@/common/db/db.service';
import { putawayRules, allocationRules, reasonCodes } from '@/db/schema';
import { AuditService } from '@/common/audit/audit.service';
import { requireRequestContext } from '@/common/context/request-context';
import { NotFoundError } from '@/common/errors/domain-error';
import { assertWarehouseAccess } from '@/common/util';

@Injectable()
export class RulesService {
  constructor(
    private readonly db: DbService,
    private readonly audit: AuditService,
  ) {}

  // ---- putaway rules ----
  listPutawayRules(warehouseId: string) {
    assertWarehouseAccess(warehouseId);
    return this.db.run((tx) => tx.select().from(putawayRules).where(eq(putawayRules.warehouseId, warehouseId)).orderBy(asc(putawayRules.priority)));
  }

  createPutawayRule(dto: CreatePutawayRuleDto) {
    assertWarehouseAccess(dto.warehouseId);
    const ctx = requireRequestContext();
    return this.db.run(async (tx) => {
      const [row] = await tx.insert(putawayRules).values({ tenantId: ctx.tenantId!, ...dto }).returning();
      await this.audit.log({ action: 'putaway_rule.create', entityType: 'putaway_rule', entityId: row.id, after: dto }, tx);
      return row;
    });
  }

  updatePutawayRule(id: string, dto: z.infer<typeof UpdatePutawayRuleSchema>) {
    return this.db.run(async (tx) => {
      const [before] = await tx.select().from(putawayRules).where(eq(putawayRules.id, id)).limit(1);
      if (!before) throw new NotFoundError('Putaway rule', id);
      const [row] = await tx.update(putawayRules).set(dto).where(eq(putawayRules.id, id)).returning();
      await this.audit.log({ action: 'putaway_rule.update', entityType: 'putaway_rule', entityId: id, before, after: row }, tx);
      return row;
    });
  }

  deletePutawayRule(id: string) {
    return this.db.run(async (tx) => {
      const [before] = await tx.delete(putawayRules).where(eq(putawayRules.id, id)).returning();
      if (!before) throw new NotFoundError('Putaway rule', id);
      await this.audit.log({ action: 'putaway_rule.delete', entityType: 'putaway_rule', entityId: id, before }, tx);
      return { ok: true };
    });
  }

  // ---- allocation rules ----
  listAllocationRules() {
    return this.db.run((tx) => tx.select().from(allocationRules).orderBy(asc(allocationRules.priority)));
  }

  createAllocationRule(dto: CreateAllocationRuleDto) {
    const ctx = requireRequestContext();
    return this.db.run(async (tx) => {
      const [row] = await tx.insert(allocationRules).values({ tenantId: ctx.tenantId!, ...dto, storerId: dto.storerId ?? null, skuId: dto.skuId ?? null }).returning();
      await this.audit.log({ action: 'allocation_rule.create', entityType: 'allocation_rule', entityId: row.id, after: dto }, tx);
      return row;
    });
  }

  updateAllocationRule(id: string, dto: z.infer<typeof UpdateAllocationRuleSchema>) {
    return this.db.run(async (tx) => {
      const [before] = await tx.select().from(allocationRules).where(eq(allocationRules.id, id)).limit(1);
      if (!before) throw new NotFoundError('Allocation rule', id);
      const [row] = await tx.update(allocationRules).set(dto).where(eq(allocationRules.id, id)).returning();
      await this.audit.log({ action: 'allocation_rule.update', entityType: 'allocation_rule', entityId: id, before, after: row }, tx);
      return row;
    });
  }

  deleteAllocationRule(id: string) {
    return this.db.run(async (tx) => {
      const [before] = await tx.delete(allocationRules).where(eq(allocationRules.id, id)).returning();
      if (!before) throw new NotFoundError('Allocation rule', id);
      await this.audit.log({ action: 'allocation_rule.delete', entityType: 'allocation_rule', entityId: id, before }, tx);
      return { ok: true };
    });
  }

  // ---- reason codes ----
  listReasonCodes(type?: string) {
    return this.db.run((tx) => tx.select().from(reasonCodes).where(and(type ? eq(reasonCodes.type, type as never) : undefined)).orderBy(reasonCodes.type, reasonCodes.code));
  }

  createReasonCode(dto: z.infer<typeof CreateReasonCodeSchema>) {
    const ctx = requireRequestContext();
    return this.db.run(async (tx) => {
      const [row] = await tx.insert(reasonCodes).values({ tenantId: ctx.tenantId!, ...dto }).returning();
      await this.audit.log({ action: 'reason_code.create', entityType: 'reason_code', entityId: row.id, after: dto }, tx);
      return row;
    });
  }

  updateReasonCode(id: string, dto: z.infer<typeof UpdateReasonCodeSchema>) {
    return this.db.run(async (tx) => {
      const [before] = await tx.select().from(reasonCodes).where(eq(reasonCodes.id, id)).limit(1);
      if (!before) throw new NotFoundError('Reason code', id);
      const [row] = await tx.update(reasonCodes).set(dto).where(eq(reasonCodes.id, id)).returning();
      await this.audit.log({ action: 'reason_code.update', entityType: 'reason_code', entityId: id, before, after: row }, tx);
      return row;
    });
  }
}
