import {
  BadRequestException,
  Injectable,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../../database/prisma.service';
import { ShopStatus } from '@prisma/client';

@Injectable()
export class ShopsService {
  private readonly logger = new Logger(ShopsService.name);

  constructor(private readonly prisma: PrismaService) {}

  private normalizeUsername(input: string) {
    let u = (input ?? '').trim();
    if (u.startsWith('@')) u = u.slice(1);
    u = u.toLowerCase();
    if (!/^[a-z0-9_.-]{3,30}$/.test(u)) {
      throw new BadRequestException('Invalid shop username');
    }
    return u;
  }

  async usernameAvailable(rawUsername: string) {
    const username = this.normalizeUsername(rawUsername);
    const count = await this.prisma.shop.count({ where: { username } });
    return { username, available: count === 0 };
  }

  async getPublicByUsername(rawUsername: string) {
    const username = this.normalizeUsername(rawUsername);
    const shop = await this.prisma.shop.findFirst({
      where: { username, status: ShopStatus.APPROVED, isActive: true },
      select: {
        id: true,
        name: true,
        username: true,
        city: true,
        state: true,
        imageUrl: true,
      },
    });
    if (!shop) throw new NotFoundException('Shop not found');
    return shop;
  }

  async listPublic(args: { q?: string; page: number; limit: number }) {
    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 rawQ = String(args.q ?? '').trim();
    const q = rawQ.startsWith('@') ? rawQ.slice(1) : rawQ;
    const where = {
      status: ShopStatus.APPROVED,
      isActive: true,
      ...(q
        ? {
            OR: [
              { name: { contains: q, mode: 'insensitive' as const } },
              {
                username: {
                  contains: q.toLowerCase(),
                  mode: 'insensitive' as const,
                },
              },
            ],
          }
        : {}),
    };
    const [total, items, suggestions] = await Promise.all([
      this.prisma.shop.count({ where }),
      this.prisma.shop.findMany({
        where,
        orderBy: [{ name: 'asc' }],
        skip,
        take: limit,
        select: {
          id: true,
          name: true,
          username: true,
          city: true,
          state: true,
          imageUrl: true,
          latitude: true,
          longitude: true,
        },
      }),
      q
        ? this.prisma.shop.findMany({
            where: {
              status: ShopStatus.APPROVED,
              isActive: true,
              username: { startsWith: q.toLowerCase(), mode: 'insensitive' },
            },
            orderBy: [{ username: 'asc' }],
            take: 8,
            select: { username: true },
          })
        : Promise.resolve([] as { username: string }[]),
    ]);
    return {
      page,
      limit,
      total,
      suggestions: suggestions.map((s) => `@${s.username}`),
      items,
    };
  }

  async listNearby(args: {
    lat: number | null;
    lng: number | null;
    take: number;
    radiusKm: number;
  }) {
    if (args.lat != null && args.lng != null) {
      this.logger.log(
        `Fetching nearby shops at latitude=${args.lat} longitude=${args.lng} (radiusKm=${args.radiusKm}, limit=${args.take})`,
      );
    } else {
      this.logger.log(
        `Fetching nearby shops without coordinates — returning featured list (limit=${args.take})`,
      );
    }

    // If no coords provided, just return newest/featured list.
    if (args.lat == null || args.lng == null) {
      const shops = await this.prisma.shop.findMany({
        where: { status: ShopStatus.APPROVED, isActive: true },
        orderBy: { createdAt: 'desc' },
        take: Math.min(args.take * 3, 150),
        select: {
          id: true,
          name: true,
          username: true,
          city: true,
          state: true,
          imageUrl: true,
          latitude: true,
          longitude: true,
        },
      });
      return { items: shops.slice(0, args.take) };
    }

    // When coords are present, compute distance across all active shops with coordinates.
    // Avoid pre-limiting by "newest", otherwise truly nearby shops can get dropped.
    const shops = await this.prisma.shop.findMany({
      where: {
        status: ShopStatus.APPROVED,
        isActive: true,
        latitude: { not: null },
        longitude: { not: null },
      },
      select: {
        id: true,
        name: true,
        username: true,
        city: true,
        state: true,
        imageUrl: true,
        latitude: true,
        longitude: true,
      },
    });

    // Haversine distance in km
    const toRad = (deg: number) => (deg * Math.PI) / 180;
    const haversineKm = (
      lat1: number,
      lon1: number,
      lat2: number,
      lon2: number,
    ) => {
      const R = 6371;
      const dLat = toRad(lat2 - lat1);
      const dLon = toRad(lon2 - lon1);
      const a =
        Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(toRad(lat1)) *
          Math.cos(toRad(lat2)) *
          Math.sin(dLon / 2) *
          Math.sin(dLon / 2);
      const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
      return R * c;
    };

    const enriched = shops
      .map((s) => {
        if (s.latitude == null || s.longitude == null)
          return { ...s, distanceKm: null as number | null };
        return {
          ...s,
          distanceKm: Number(
            haversineKm(args.lat!, args.lng!, s.latitude, s.longitude).toFixed(
              2,
            ),
          ),
        };
      })
      .filter((s) => s.distanceKm != null && s.distanceKm <= args.radiusKm)
      .sort((a, b) => {
        if (a.distanceKm == null && b.distanceKm == null) return 0;
        if (a.distanceKm == null) return 1;
        if (b.distanceKm == null) return -1;
        return a.distanceKm - b.distanceKm;
      });

    return { items: enriched.slice(0, args.take) };
  }
}
