import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateReceiptAliasDto } from './dto/create-receipt-alias.dto';
import { UpdateReceiptAliasDto } from './dto/update-receipt-alias.dto';
import {
  normalizeReceiptAliasName,
  validateReceiptAliasName,
} from '../common/utils/receipt-alias';

@Injectable()
export class ReceiptAliasService {
  constructor(private readonly prisma: PrismaService) {}

  findAllForUser(userId: number, role: string) {
    const where = role === 'admin'
      ? undefined
      : {
          OR: [
            { ownerId: userId, isGlobal: false },
            { isGlobal: true },
          ],
        };

    return this.prisma.receiptAlias.findMany({
      where,
      include: { product: { select: { id: true, name: true, canonicalName: true } } },
      orderBy: { receiptName: 'asc' },
    });
  }

  async upsert(dto: CreateReceiptAliasDto, userId: number, role: string) {
    const normalized = normalizeReceiptAliasName(dto.receiptName);
    const validationError = validateReceiptAliasName(normalized);
    if (validationError) {
      throw new BadRequestException(validationError);
    }
    const wantsGlobal = dto.isGlobal === true;

    if (wantsGlobal && role !== 'admin') {
      throw new ForbiddenException('Endast admin kan skapa globala alias');
    }

    return this.upsertAliasRecord(
      normalized,
      dto.productId,
      wantsGlobal ? null : userId,
      wantsGlobal,
    );
  }

  private async upsertAliasRecord(
    receiptName: string,
    productId: number,
    ownerId: number | null,
    isGlobal: boolean,
  ) {
    const normalizedReceiptName = normalizeReceiptAliasName(receiptName);
    const existing = await this.prisma.receiptAlias.findFirst({
      where: isGlobal
        ? { receiptName: normalizedReceiptName, isGlobal: true }
        : { receiptName: normalizedReceiptName, ownerId, isGlobal: false },
    });

    if (existing) {
      return this.prisma.receiptAlias.update({
        where: { id: existing.id },
        data: { productId },
      });
    }

    return this.prisma.receiptAlias.create({
      data: { receiptName: normalizedReceiptName, productId, ownerId, isGlobal },
    });
  }

  async remove(id: number, userId: number, role: string) {
    const alias = await this.prisma.receiptAlias.findUnique({ where: { id } });
    if (!alias) {
      throw new NotFoundException(`Aliaspost med id ${id} hittades inte`);
    }

    const canDelete =
      role === 'admin' ||
      (alias.ownerId === userId && alias.isGlobal === false);

    if (!canDelete) {
      throw new ForbiddenException('Du har inte behörighet att ta bort aliaset');
    }

    return this.prisma.receiptAlias.delete({ where: { id } });
  }

  async update(
    id: number,
    dto: UpdateReceiptAliasDto,
    userId: number,
    role: string,
  ) {
    const alias = await this.prisma.receiptAlias.findUnique({ where: { id } });
    if (!alias) {
      throw new NotFoundException(`Aliaspost med id ${id} hittades inte`);
    }

    const canEdit =
      role === 'admin' ||
      (alias.ownerId === userId && alias.isGlobal === false);

    if (!canEdit) {
      throw new ForbiddenException('Du har inte behörighet att redigera aliaset');
    }

    if (dto.receiptName == null && dto.productId == null && dto.isGlobal == null) {
      throw new BadRequestException('Inget att uppdatera. Ange receiptName, productId eller isGlobal.');
    }

    let nextReceiptName = alias.receiptName;
    if (dto.receiptName != null) {
      nextReceiptName = normalizeReceiptAliasName(dto.receiptName);
      const validationError = validateReceiptAliasName(nextReceiptName);
      if (validationError) {
        throw new BadRequestException(validationError);
      }
    }

    const nextProductId = dto.productId ?? alias.productId;
    const nextIsGlobal = dto.isGlobal ?? alias.isGlobal;

    if (dto.isGlobal != null && dto.isGlobal !== alias.isGlobal && role !== 'admin') {
      throw new ForbiddenException('Endast admin kan ändra aliasets scope (privat/global).');
    }

    const nextOwnerId = nextIsGlobal ? null : alias.ownerId;
    if (!nextIsGlobal && nextOwnerId == null) {
      throw new BadRequestException('Kan inte göra globalt alias privat utan ägare.');
    }

    // Skydda mot krock i samma scope när namn eller scope ändras.
    if (
      nextReceiptName !== alias.receiptName ||
      nextIsGlobal !== alias.isGlobal ||
      nextOwnerId !== alias.ownerId
    ) {
      const conflict = await this.prisma.receiptAlias.findFirst({
        where: nextIsGlobal
          ? { receiptName: nextReceiptName, isGlobal: true }
          : { receiptName: nextReceiptName, ownerId: nextOwnerId, isGlobal: false },
      });

      if (conflict && conflict.id !== alias.id) {
        throw new BadRequestException('Aliasnamnet finns redan i samma scope.');
      }
    }

    return this.prisma.receiptAlias.update({
      where: { id: alias.id },
      data: {
        receiptName: nextReceiptName,
        productId: nextProductId,
        isGlobal: nextIsGlobal,
        ownerId: nextOwnerId,
      },
    });
  }
}
