import { ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { MerchantProviderConfig, Prisma } from "@prisma/client";
import { EncryptionService, EncryptedPayload } from "../common/crypto/encryption.service";
import { newId } from "../common/ids/id.util";
import { PrismaService } from "../prisma/prisma.service";
import type { CreateProviderConfigDto } from "./dto/create-provider-config.dto";
import type { UpdateProviderConfigDto } from "./dto/update-provider-config.dto";

@Injectable()
export class ProviderConfigService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly encryption: EncryptionService,
  ) {}

  async create(merchantId: string, dto: CreateProviderConfigDto) {
    const encrypted = this.encryption.encryptJson(dto.credentials);
    try {
      const row = await this.prisma.merchantProviderConfig.create({
        data: {
          id: newId("providerConfig"),
          merchantId,
          provider: dto.provider,
          mode: dto.mode,
          country: dto.country,
          credentialsEncrypted: encrypted as unknown as Prisma.InputJsonValue,
          fxStrategy: dto.fxStrategy ?? "delegate_provider",
          priority: dto.priority ?? 0,
          active: dto.active ?? true,
        },
      });
      return this.toDto(row);
    } catch (err) {
      if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
        throw new ConflictException(
          "Provider config already exists for this (provider, country, mode) combination",
        );
      }
      throw err;
    }
  }

  async findOne(merchantId: string, id: string) {
    const row = await this.prisma.merchantProviderConfig.findFirst({ where: { id, merchantId } });
    if (!row) throw new NotFoundException("Provider config not found");
    return this.toDto(row);
  }

  async list(merchantId: string, limit = 20, cursor?: string) {
    const take = Math.min(Math.max(limit, 1), 100);
    const rows = await this.prisma.merchantProviderConfig.findMany({
      where: { merchantId },
      take: take + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: [{ priority: "desc" }, { createdAt: "desc" }],
    });
    const hasMore = rows.length > take;
    const data = hasMore ? rows.slice(0, take) : rows;
    return {
      data: data.map((r) => this.toDto(r)),
      nextCursor: hasMore ? data[data.length - 1].id : null,
    };
  }

  async update(merchantId: string, id: string, dto: UpdateProviderConfigDto) {
    await this.findOneRaw(merchantId, id);
    const updated = await this.prisma.merchantProviderConfig.update({
      where: { id },
      data: {
        ...(dto.credentials !== undefined && {
          credentialsEncrypted: this.encryption.encryptJson(
            dto.credentials,
          ) as unknown as Prisma.InputJsonValue,
        }),
        ...(dto.fxStrategy !== undefined && { fxStrategy: dto.fxStrategy }),
        ...(dto.priority !== undefined && { priority: dto.priority }),
        ...(dto.active !== undefined && { active: dto.active }),
      },
    });
    return this.toDto(updated);
  }

  async remove(merchantId: string, id: string) {
    await this.findOneRaw(merchantId, id);
    await this.prisma.merchantProviderConfig.delete({ where: { id } });
    return { id, deleted: true };
  }

  /** Uso interno (otros services) — devuelve credenciales descifradas. */
  async getDecryptedCredentials(merchantId: string, id: string): Promise<Record<string, unknown>> {
    const row = await this.findOneRaw(merchantId, id);
    return this.encryption.decryptJson<Record<string, unknown>>(
      row.credentialsEncrypted as unknown as EncryptedPayload,
    );
  }

  private async findOneRaw(merchantId: string, id: string): Promise<MerchantProviderConfig> {
    const row = await this.prisma.merchantProviderConfig.findFirst({ where: { id, merchantId } });
    if (!row) throw new NotFoundException("Provider config not found");
    return row;
  }

  private toDto(row: MerchantProviderConfig) {
    return {
      id: row.id,
      merchantId: row.merchantId,
      provider: row.provider,
      mode: row.mode,
      country: row.country,
      fxStrategy: row.fxStrategy,
      priority: row.priority,
      active: row.active,
      hasCredentials: row.credentialsEncrypted !== null,
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    };
  }
}
