import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { RequirementStatus } from '@ftb/shared';
import { CreateRequirementDto } from './dto/create-requirement.dto';
import { UpdateRequirementDto } from './dto/update-requirement.dto';

const VALID_TRANSITIONS: Record<string, string[]> = {
  [RequirementStatus.PENDING_REVIEW]: [RequirementStatus.ADOPTED, RequirementStatus.REJECTED],
  [RequirementStatus.REJECTED]: [RequirementStatus.PENDING_REVIEW],
  [RequirementStatus.ADOPTED]: [RequirementStatus.PLANNED, RequirementStatus.CLOSED],
  [RequirementStatus.PLANNED]: [RequirementStatus.DEVELOPING, RequirementStatus.CLOSED],
  [RequirementStatus.DEVELOPING]: [RequirementStatus.TESTING],
  [RequirementStatus.TESTING]: [RequirementStatus.RELEASED],
  [RequirementStatus.RELEASED]: [RequirementStatus.CLOSED],
  [RequirementStatus.CLOSED]: [],
};

export interface RequirementListQuery {
  projectId?: string;
  versionId?: string;
  status?: string;
  priority?: string;
  type?: string;
  q?: string;
  sort?: string;
  cursor?: string;
  limit?: string | number;
}

function createFallbackRequirementCode() {
  return `REQ-${Date.now().toString(36).toUpperCase()}-${Math.random()
    .toString(36)
    .slice(2, 6)
    .toUpperCase()}`;
}

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

  create(productId: string, dto: CreateRequirementDto) {
    const data = this.toRequirementData(dto);
    return this.prisma.requirement.create({
      data: {
        ...data,
        productId,
        code: dto.code?.trim() || createFallbackRequirementCode(),
        title: dto.title,
        description: dto.description ?? '',
        status: dto.status ?? RequirementStatus.PENDING_REVIEW,
        priority: parsePriorityValue(dto.priority) ?? 0,
      },
    });
  }

  async findAll(productId: string, query: RequirementListQuery = {}) {
    const normalizedProductId = productId?.trim();
    if (!normalizedProductId) {
      throw new BadRequestException('productId is required for requirement pool queries');
    }

    const limit = parseLimit(query.limit);
    const priority = parsePriorityValue(query.priority);
    const search = query.q?.trim();
    const rows = await this.prisma.requirement.findMany({
      where: {
        productId: normalizedProductId,
        ...(query.projectId?.trim() ? { projectId: query.projectId.trim() } : {}),
        ...(query.versionId?.trim() ? { versionId: query.versionId.trim() } : {}),
        ...(query.status?.trim() ? { status: query.status.trim() } : {}),
        ...(priority !== undefined ? { priority } : {}),
        ...(query.type?.trim() ? { type: query.type.trim() } : {}),
        ...(search
          ? {
            OR: [
              { code: { contains: search, mode: 'insensitive' as const } },
              { title: { contains: search, mode: 'insensitive' as const } },
            ],
          }
          : {}),
      },
      include: { creator: { select: { id: true, name: true } } },
      orderBy: parseRequirementSort(query.sort),
      take: limit + 1,
      ...(query.cursor
        ? {
          cursor: { id_productId: { id: query.cursor, productId: normalizedProductId } },
          skip: 1,
        }
        : {}),
    });

    const hasNext = rows.length > limit;
    return {
      items: hasNext ? rows.slice(0, limit) : rows,
      nextCursor: hasNext ? rows[limit]?.id : undefined,
    };
  }

  async findOne(productId: string, id: string) {
    const req = await this.prisma.requirement.findFirst({
      where: { id, productId },
      include: { creator: { select: { id: true, name: true } } },
    });
    if (!req) throw new NotFoundException('需求不存在');
    return req;
  }

  async update(productId: string, id: string, dto: UpdateRequirementDto) {
    await this.findOne(productId, id);
    return this.prisma.requirement.update({
      where: { id_productId: { id, productId } },
      data: this.toRequirementData(dto),
    });
  }

  async updateStatus(productId: string, id: string, newStatus: string) {
    const req = await this.findOne(productId, id);
    const allowed = VALID_TRANSITIONS[req.status] || [];
    if (!allowed.includes(newStatus)) {
      throw new BadRequestException(
        `无法从 "${req.status}" 转换到 "${newStatus}"`,
      );
    }
    return this.prisma.requirement.update({
      where: { id_productId: { id, productId } },
      data: { status: newStatus },
    });
  }

  async remove(productId: string, id: string) {
    await this.findOne(productId, id);
    return this.prisma.requirement.delete({ where: { id_productId: { id, productId } } });
  }

  private toRequirementData(dto: CreateRequirementDto | UpdateRequirementDto) {
    const type = dto.type ?? dto.typeId;
    const platform = dto.platform ?? arrayToCsv(dto.platforms);
    const priority = parsePriorityValue(dto.priority);

    return {
      ...(dto.code !== undefined && { code: dto.code }),
      ...(dto.title !== undefined && { title: dto.title }),
      ...(dto.description !== undefined && { description: dto.description }),
      ...(dto.projectId !== undefined && { projectId: emptyToNull(dto.projectId) }),
      ...(dto.versionId !== undefined && { versionId: emptyToNull(dto.versionId) }),
      ...(type !== undefined && { type: emptyToNull(type) }),
      ...(dto.sourceType !== undefined && { sourceType: emptyToNull(dto.sourceType) }),
      ...(dto.sourceTarget !== undefined && { sourceTarget: emptyToNull(dto.sourceTarget) }),
      ...(platform !== undefined && { platform: emptyToNull(platform) }),
      ...(dto.creatorId !== undefined && { creatorId: emptyToNull(dto.creatorId) }),
      ...(priority !== undefined && { priority }),
    };
  }
}

function emptyToNull(value: string | null | undefined): string | null {
  if (value === null) return null;
  if (value === undefined) return null;
  const trimmed = value.trim();
  return trimmed ? trimmed : null;
}

function arrayToCsv(value: string[] | undefined): string | undefined {
  if (!value) return undefined;
  return value.map((item) => item.trim()).filter(Boolean).join(',');
}

function parseLimit(raw?: string | number): number {
  const parsed = raw === undefined ? 50 : Number(raw);
  if (!Number.isFinite(parsed)) return 50;
  return Math.max(1, Math.min(200, Math.floor(parsed)));
}

function parsePriorityValue(raw?: string | number | null): number | undefined {
  if (raw === null || raw === undefined || raw === '') return undefined;
  if (typeof raw === 'number') return Number.isFinite(raw) ? Math.max(0, Math.min(4, Math.floor(raw))) : undefined;
  const normalized = raw.trim().toUpperCase();
  const prefixed = /^P([0-4])$/.exec(normalized);
  if (prefixed) return Number(prefixed[1]);
  const parsed = Number(normalized);
  if (!Number.isFinite(parsed)) return undefined;
  return Math.max(0, Math.min(4, Math.floor(parsed)));
}

function parseRequirementSort(sort?: string) {
  switch (sort) {
    case 'created_at_asc':
      return { createdAt: 'asc' as const };
    case 'priority_asc':
      return { priority: 'asc' as const };
    case 'priority_desc':
      return { priority: 'desc' as const };
    case 'code_asc':
      return { code: 'asc' as const };
    case 'code_desc':
      return { code: 'desc' as const };
    case 'updated_at_asc':
      return { updatedAt: 'asc' as const };
    case 'updated_at_desc':
      return { updatedAt: 'desc' as const };
    case 'created_at_desc':
    default:
      return { createdAt: 'desc' as const };
  }
}
