import {
  BadRequestException,
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../../database/prisma.service';
import { DEFAULT_PLANS } from './plans.defaults';
import { Prisma } from '@prisma/client';

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

  list() {
    return this.prisma.plan.findMany({
      where: { isActive: true },
      orderBy: { createdAt: 'asc' },
      select: {
        id: true,
        code: true,
        name: true,
        priceMonthly: true,
        currency: true,
        features: true,
        isActive: true,
      },
    });
  }

  async listPaged(args: {
    page: number;
    limit: number;
    q?: string;
    code?: string;
    isActive?: string;
  }) {
    const page = Math.max(1, Math.floor(args.page || 1));
    const limit = Math.min(100, Math.max(1, Math.floor(args.limit || 20)));
    const skip = (page - 1) * limit;
    const q = String(args.q ?? '').trim();
    const code = String(args.code ?? '').trim();
    const isActiveRaw =
      args.isActive != null ? String(args.isActive).trim().toLowerCase() : '';

    let isActive: boolean | undefined;
    if (isActiveRaw === 'true') isActive = true;
    else if (isActiveRaw === 'false') isActive = false;

    const where: Prisma.PlanWhereInput = {};
    if (code) where.code = code.toUpperCase();
    if (isActive !== undefined) where.isActive = isActive;
    if (q) {
      where.OR = [
        { name: { contains: q, mode: 'insensitive' } },
        { code: { contains: q.toUpperCase() } },
      ];
    }

    const [total, items] = await this.prisma.$transaction([
      this.prisma.plan.count({ where }),
      this.prisma.plan.findMany({
        where,
        orderBy: { createdAt: 'asc' },
        skip,
        take: limit,
        select: {
          id: true,
          code: true,
          name: true,
          priceMonthly: true,
          currency: true,
          features: true,
          isActive: true,
          createdAt: true,
          updatedAt: true,
        },
      }),
    ]);

    return { page, limit, total, items };
  }

  private normalizeFeatures(features: unknown) {
    if (features == null) return [];
    if (!Array.isArray(features))
      throw new BadRequestException('features must be an array');
    return features
      .map((f) => {
        if (!f || typeof f !== 'object') return null;
        const rec = f as Record<string, unknown>;
        const k = String(rec.k ?? '').trim();
        const v = String(rec.v ?? '').trim();
        if (!k) return null;
        return { k, v };
      })
      .filter(Boolean);
  }

  async upsertByCode(dto: {
    code: string;
    name: string;
    priceMonthly?: number;
    currency?: string;
    features?: any[];
    isActive?: boolean;
  }) {
    const code = String(dto.code || '')
      .trim()
      .toUpperCase();
    if (!code) throw new BadRequestException('code is required');
    const name = String(dto.name || '').trim();
    if (!name) throw new BadRequestException('name is required');
    const priceMonthly =
      dto.priceMonthly != null && Number.isFinite(Number(dto.priceMonthly))
        ? Math.max(0, Math.floor(Number(dto.priceMonthly)))
        : 0;
    const currency = dto.currency
      ? String(dto.currency).trim().toUpperCase()
      : 'INR';
    const features = this.normalizeFeatures(dto.features ?? []);
    const isActive = dto.isActive !== undefined ? Boolean(dto.isActive) : true;

    const plan = await this.prisma.plan.upsert({
      where: { code },
      create: { code, name, priceMonthly, currency, features, isActive },
      update: { name, priceMonthly, currency, features, isActive },
      select: {
        id: true,
        code: true,
        name: true,
        priceMonthly: true,
        currency: true,
        features: true,
        isActive: true,
        createdAt: true,
        updatedAt: true,
      },
    });

    return { upserted: true, plan };
  }

  async update(
    id: string,
    dto: {
      code?: string;
      name?: string;
      priceMonthly?: number;
      currency?: string;
      features?: any[];
      isActive?: boolean;
    },
  ) {
    const existing = await this.prisma.plan.findUnique({
      where: { id },
      select: { id: true },
    });
    if (!existing) throw new NotFoundException('Plan not found');

    const data: Prisma.PlanUpdateInput = {};
    if (dto.code !== undefined) {
      const c = String(dto.code || '')
        .trim()
        .toUpperCase();
      if (!c) throw new BadRequestException('code cannot be empty');
      data.code = c;
    }
    if (dto.name !== undefined) {
      const n = String(dto.name || '').trim();
      if (!n) throw new BadRequestException('name cannot be empty');
      data.name = n;
    }
    if (dto.priceMonthly !== undefined) {
      const p = Number(dto.priceMonthly);
      if (!Number.isFinite(p) || p < 0)
        throw new BadRequestException('Invalid priceMonthly');
      data.priceMonthly = Math.floor(p);
    }
    if (dto.currency !== undefined) {
      const c = String(dto.currency || '')
        .trim()
        .toUpperCase();
      if (!c) throw new BadRequestException('currency cannot be empty');
      data.currency = c;
    }
    if (dto.features !== undefined) {
      data.features = this.normalizeFeatures(dto.features) as any;
    }
    if (dto.isActive !== undefined) {
      data.isActive = Boolean(dto.isActive);
    }

    try {
      const plan = await this.prisma.plan.update({
        where: { id },
        data,
        select: {
          id: true,
          code: true,
          name: true,
          priceMonthly: true,
          currency: true,
          features: true,
          isActive: true,
          createdAt: true,
          updatedAt: true,
        },
      });
      return { updated: true, plan };
    } catch (e) {
      // Most common: unique constraint on code.
      throw new ConflictException('Plan code already exists');
    }
  }

  async remove(id: string) {
    const existing = await this.prisma.plan.findUnique({
      where: { id },
      select: { id: true },
    });
    if (!existing) throw new NotFoundException('Plan not found');
    await this.prisma.plan.delete({ where: { id } });
    return { deleted: true };
  }

  async ensureDefaultsSeededIfEmpty() {
    const count = await this.prisma.plan.count();
    if (count > 0) return { seeded: 0, alreadySeeded: true };
    const res = await this.seedDefaults();
    return { ...res, alreadySeeded: false };
  }

  async ensureDefaultsSeededIfMissingOrLegacy() {
    const existing = await this.prisma.plan.findMany({
      where: { code: { in: ['BASIC', 'GROWTH', 'PRO'] } },
      select: { code: true, features: true },
    });

    const byCode = new Map(existing.map((p) => [p.code, p]));
    const missing =
      !byCode.has('BASIC') || !byCode.has('GROWTH') || !byCode.has('PRO');

    const isKvArray = (features: unknown): boolean => {
      if (!Array.isArray(features) || features.length === 0) return false;
      return features.every((f) => {
        if (f == null || typeof f !== 'object') return false;
        const rec = f as Record<string, unknown>;
        return typeof rec.k === 'string' && typeof rec.v === 'string';
      });
    };

    const legacy =
      existing.length > 0 && existing.some((p) => !isKvArray(p.features));

    const missingKeys = (() => {
      if (existing.length === 0) return false;
      const expectedByCode = new Map(
        DEFAULT_PLANS.map(
          (p) => [p.code, new Set(p.features.map((f) => f.k))] as const,
        ),
      );
      return existing.some((p) => {
        const expected = expectedByCode.get(p.code);
        if (!expected) return false;
        if (!Array.isArray(p.features)) return true;
        const keys = new Set(
          (p.features as any[])
            .map((f) => (f && typeof f === 'object' ? f.k : null))
            .filter((k) => typeof k === 'string'),
        );
        for (const k of expected) {
          if (!keys.has(k)) return true;
        }
        return false;
      });
    })();

    if (missing || legacy || missingKeys) {
      return {
        ...(await this.seedDefaults()),
        alreadySeeded: existing.length > 0,
      };
    }
    return { seeded: 0, alreadySeeded: true };
  }

  async seedDefaults() {
    const results: Array<{ id: string; code: string; name: string }> = [];
    for (const plan of DEFAULT_PLANS) {
      const upserted = await this.prisma.plan.upsert({
        where: { code: plan.code },
        create: {
          code: plan.code,
          name: plan.name,
          priceMonthly: plan.priceMonthly,
          currency: plan.currency,
          features: plan.features,
          isActive: true,
        },
        update: {
          name: plan.name,
          priceMonthly: plan.priceMonthly,
          currency: plan.currency,
          features: plan.features,
          isActive: true,
        },
        select: { id: true, code: true, name: true },
      });
      results.push(upserted);
    }
    return { seeded: results.length, plans: results };
  }
}
