import { Injectable } from '@nestjs/common';
import { createId } from '@paralleldrive/cuid2';
import type { Prisma } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import type { CreateTransferItemInput } from '../../application/commands/create-transfer-listing/create-transfer-listing.command';
import { TransferListingEntity } from '../../domain/entities/transfer-listing.entity';
import type {
  ITransferListingRepository,
  TransferListingSearchParams,
  PaginatedResult,
  TransferListingListItem,
  TransferListingDetailData,
  TransferItemData,
  TransferStatsData,
} from '../../domain/repositories/transfer-listing.repository';

interface RawTransferListing {
  id: string;
  sellerId: string;
  category: string;
  status: string;
  title: string;
  description: string | null;
  address: string;
  ward: string | null;
  district: string;
  city: string;
  lat: number;
  lng: number;
  askingPriceVND: bigint;
  aiEstimatePriceVND: bigint | null;
  aiConfidence: number | null;
  pricingSource: string;
  isNegotiable: boolean;
  areaM2: number | null;
  monthlyRentVND: bigint | null;
  depositMonths: number | null;
  remainingLeaseMo: number | null;
  businessType: string | null;
  footTraffic: string | null;
  media: Prisma.JsonValue;
  moderationScore: number | null;
  moderationNotes: string | null;
  viewCount: number;
  saveCount: number;
  inquiryCount: number;
  contactPhone: string | null;
  contactName: string | null;
  featuredUntil: Date | null;
  expiresAt: Date | null;
  publishedAt: Date | null;
  createdAt: Date;
  updatedAt: Date;
}

interface RawTransferListingWithCount extends RawTransferListing {
  itemCount: number;
}

interface RawTransferItem {
  id: string;
  transferListingId: string;
  name: string;
  brand: string | null;
  modelName: string | null;
  category: string;
  condition: string;
  purchaseYear: number | null;
  originalPriceVND: bigint | null;
  askingPriceVND: bigint;
  aiEstimatePriceVND: bigint | null;
  aiConfidence: number | null;
  quantity: number;
  dimensions: Prisma.JsonValue;
  media: Prisma.JsonValue;
  notes: string | null;
  createdAt: Date;
  updatedAt: Date;
}


@Injectable()
export class PrismaTransferListingRepository implements ITransferListingRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string): Promise<TransferListingEntity | null> {
    const rows = await this.prisma.$queryRaw<RawTransferListing[]>`
      SELECT *, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng
      FROM "TransferListing" WHERE id = ${id} LIMIT 1
    `;
    return rows[0] ? this.toDomain(rows[0]) : null;
  }

  async findDetailById(id: string): Promise<TransferListingDetailData | null> {
    const rows = await this.prisma.$queryRaw<RawTransferListing[]>`
      SELECT *, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng
      FROM "TransferListing" WHERE id = ${id} LIMIT 1
    `;

    if (!rows[0]) return null;

    const items = await this.prisma.$queryRaw<RawTransferItem[]>`
      SELECT * FROM "TransferItem" WHERE "transferListingId" = ${id}
    `;

    return this.toDetail(rows[0], items);
  }

  async save(entity: TransferListingEntity, items: CreateTransferItemInput[]): Promise<void> {
    await this.prisma.$transaction(async (tx) => {
      await tx.$executeRaw`
        INSERT INTO "TransferListing" (
          id, "sellerId", category, status, title, description,
          address, ward, district, city, location,
          "askingPriceVND", "aiEstimatePriceVND", "aiConfidence", "pricingSource",
          "isNegotiable", "areaM2", "monthlyRentVND", "depositMonths",
          "remainingLeaseMo", "businessType", "footTraffic", media,
          "moderationScore", "moderationNotes",
          "viewCount", "saveCount", "inquiryCount",
          "contactPhone", "contactName",
          "featuredUntil", "expiresAt", "publishedAt",
          "createdAt", "updatedAt"
        ) VALUES (
          ${entity.id}, ${entity.sellerId},
          ${entity.category}::"TransferCategory",
          ${entity.status}::"TransferListingStatus",
          ${entity.title}, ${entity.description},
          ${entity.address}, ${entity.ward}, ${entity.district}, ${entity.city},
          ST_SetSRID(ST_MakePoint(${entity.longitude}, ${entity.latitude}), 4326),
          ${entity.askingPriceVND}, ${entity.aiEstimatePriceVND}, ${entity.aiConfidence},
          ${entity.pricingSource}::"TransferPricingSource",
          ${entity.isNegotiable}, ${entity.areaM2}, ${entity.monthlyRentVND},
          ${entity.depositMonths}, ${entity.remainingLeaseMo},
          ${entity.businessType}, ${entity.footTraffic},
          ${entity.media ? JSON.stringify(entity.media) : null}::jsonb,
          ${entity.moderationScore}, ${entity.moderationNotes},
          ${entity.viewCount}, ${entity.saveCount}, ${entity.inquiryCount},
          ${entity.contactPhone}, ${entity.contactName},
          ${entity.featuredUntil}, ${entity.expiresAt}, ${entity.publishedAt},
          ${entity.createdAt}, ${entity.updatedAt}
        )
      `;

      if (items.length > 0) {
        const now = new Date();
        for (const item of items) {
          const itemId = createId();
          await tx.$executeRaw`
            INSERT INTO "TransferItem" (
              id, "transferListingId", name, brand, "modelName",
              category, condition, "purchaseYear",
              "originalPriceVND", "askingPriceVND",
              "aiEstimatePriceVND", "aiConfidence",
              quantity, dimensions, media, notes,
              "createdAt", "updatedAt"
            ) VALUES (
              ${itemId}, ${entity.id},
              ${item.name}, ${item.brand ?? null}, ${item.modelName ?? null},
              ${item.category}::"TransferCategory",
              ${item.condition}::"TransferCondition",
              ${item.purchaseYear ?? null},
              ${item.originalPriceVND ?? null}, ${item.askingPriceVND},
              ${null}::bigint, ${null}::float8,
              ${item.quantity ?? 1},
              ${item.dimensions ? JSON.stringify(item.dimensions) : null}::jsonb,
              ${null}::jsonb,
              ${item.notes ?? null},
              ${now}, ${now}
            )
          `;
        }
      }
    });
  }

  async update(entity: TransferListingEntity): Promise<void> {
    await this.prisma.$executeRaw`
      UPDATE "TransferListing" SET
        "sellerId" = ${entity.sellerId},
        category = ${entity.category}::"TransferCategory",
        status = ${entity.status}::"TransferListingStatus",
        title = ${entity.title},
        description = ${entity.description},
        address = ${entity.address},
        ward = ${entity.ward},
        district = ${entity.district},
        city = ${entity.city},
        location = ST_SetSRID(ST_MakePoint(${entity.longitude}, ${entity.latitude}), 4326),
        "askingPriceVND" = ${entity.askingPriceVND},
        "aiEstimatePriceVND" = ${entity.aiEstimatePriceVND},
        "aiConfidence" = ${entity.aiConfidence},
        "pricingSource" = ${entity.pricingSource}::"TransferPricingSource",
        "isNegotiable" = ${entity.isNegotiable},
        "areaM2" = ${entity.areaM2},
        "monthlyRentVND" = ${entity.monthlyRentVND},
        "depositMonths" = ${entity.depositMonths},
        "remainingLeaseMo" = ${entity.remainingLeaseMo},
        "businessType" = ${entity.businessType},
        "footTraffic" = ${entity.footTraffic},
        media = ${entity.media ? JSON.stringify(entity.media) : null}::jsonb,
        "moderationScore" = ${entity.moderationScore},
        "moderationNotes" = ${entity.moderationNotes},
        "viewCount" = ${entity.viewCount},
        "saveCount" = ${entity.saveCount},
        "inquiryCount" = ${entity.inquiryCount},
        "contactPhone" = ${entity.contactPhone},
        "contactName" = ${entity.contactName},
        "featuredUntil" = ${entity.featuredUntil},
        "expiresAt" = ${entity.expiresAt},
        "publishedAt" = ${entity.publishedAt},
        "updatedAt" = ${entity.updatedAt}
      WHERE id = ${entity.id}
    `;
  }

  async search(params: TransferListingSearchParams): Promise<PaginatedResult<TransferListingListItem>> {
    const page = params.page ?? 1;
    const limit = params.limit ?? 20;
    const offset = (page - 1) * limit;

    const conditions: string[] = ['1=1'];
    const values: unknown[] = [];
    let paramIndex = 1;

    if (params.category) {
      conditions.push(`category = $${paramIndex++}::"TransferCategory"`);
      values.push(params.category);
    }
    if (params.status) {
      conditions.push(`status = $${paramIndex++}::"TransferListingStatus"`);
      values.push(params.status);
    }
    if (params.district) {
      conditions.push(`district = $${paramIndex++}`);
      values.push(params.district);
    }
    if (params.city) {
      conditions.push(`city = $${paramIndex++}`);
      values.push(params.city);
    }
    if (params.minPrice != null) {
      conditions.push(`"askingPriceVND" >= $${paramIndex++}`);
      values.push(params.minPrice);
    }
    if (params.maxPrice != null) {
      conditions.push(`"askingPriceVND" <= $${paramIndex++}`);
      values.push(params.maxPrice);
    }
    if (params.query) {
      conditions.push(
        `(title ILIKE $${paramIndex} OR description ILIKE $${paramIndex} OR address ILIKE $${paramIndex} OR district ILIKE $${paramIndex})`,
      );
      values.push(`%${params.query}%`);
      paramIndex++;
    }

    const where = conditions.join(' AND ');

    const countResult = await this.prisma.$queryRawUnsafe<[{ count: bigint }]>(
      `SELECT COUNT(*)::bigint as count FROM "TransferListing" WHERE ${where}`,
      ...values,
    );
    const total = Number(countResult[0].count);

    const rows = await this.prisma.$queryRawUnsafe<RawTransferListingWithCount[]>(
      `SELECT t.*, ST_Y(t.location::geometry) as lat, ST_X(t.location::geometry) as lng,
        (SELECT COUNT(*) FROM "TransferItem" WHERE "transferListingId" = t.id)::int as "itemCount"
       FROM "TransferListing" t WHERE ${where}
       ORDER BY "publishedAt" DESC NULLS LAST, t."createdAt" DESC
       LIMIT $${paramIndex++} OFFSET $${paramIndex}`,
      ...values, limit, offset,
    );

    return {
      data: rows.map((r) => this.toListItem(r)),
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async getStats(): Promise<TransferStatsData> {
    const [summary] = await this.prisma.$queryRaw<[{ totalListings: bigint; totalValue: bigint }]>`
      SELECT COUNT(*)::bigint as "totalListings",
        COALESCE(SUM("askingPriceVND"), 0)::bigint as "totalValue"
      FROM "TransferListing" WHERE status = 'ACTIVE'
    `;

    const byCategory = await this.prisma.$queryRaw<{ category: string; count: bigint; avgPrice: number }[]>`
      SELECT category::text, COUNT(*)::bigint as count,
        AVG("askingPriceVND"::numeric)::float as "avgPrice"
      FROM "TransferListing" WHERE status = 'ACTIVE'
      GROUP BY category ORDER BY count DESC
    `;

    const byDistrict = await this.prisma.$queryRaw<{ district: string; count: bigint; avgPrice: number }[]>`
      SELECT district, COUNT(*)::bigint as count,
        AVG("askingPriceVND"::numeric)::float as "avgPrice"
      FROM "TransferListing" WHERE status = 'ACTIVE'
      GROUP BY district ORDER BY count DESC
    `;

    const byStatus = await this.prisma.$queryRaw<{ status: string; count: bigint }[]>`
      SELECT status::text, COUNT(*)::bigint as count
      FROM "TransferListing" GROUP BY status ORDER BY count DESC
    `;

    return {
      totalListings: Number(summary.totalListings),
      totalValue: summary.totalValue,
      byCategory: byCategory.map((r) => ({ category: r.category, count: Number(r.count), avgPrice: r.avgPrice })),
      byDistrict: byDistrict.map((r) => ({ district: r.district, count: Number(r.count), avgPrice: r.avgPrice })),
      byStatus: byStatus.map((r) => ({ status: r.status, count: Number(r.count) })),
    };
  }

  private toDomain(row: RawTransferListing): TransferListingEntity {
    return new TransferListingEntity(
      row.id,
      {
        sellerId: row.sellerId,
        category: row.category as TransferListingEntity['category'],
        status: row.status as TransferListingEntity['status'],
        title: row.title,
        description: row.description,
        address: row.address,
        ward: row.ward,
        district: row.district,
        city: row.city,
        latitude: Number(row.lat),
        longitude: Number(row.lng),
        askingPriceVND: row.askingPriceVND,
        aiEstimatePriceVND: row.aiEstimatePriceVND,
        aiConfidence: row.aiConfidence,
        pricingSource: row.pricingSource as TransferListingEntity['pricingSource'],
        isNegotiable: row.isNegotiable,
        areaM2: row.areaM2,
        monthlyRentVND: row.monthlyRentVND,
        depositMonths: row.depositMonths,
        remainingLeaseMo: row.remainingLeaseMo,
        businessType: row.businessType,
        footTraffic: row.footTraffic,
        media: row.media as Record<string, unknown>[] | null,
        moderationScore: row.moderationScore,
        moderationNotes: row.moderationNotes,
        viewCount: row.viewCount,
        saveCount: row.saveCount,
        inquiryCount: row.inquiryCount,
        contactPhone: row.contactPhone,
        contactName: row.contactName,
        featuredUntil: row.featuredUntil,
        expiresAt: row.expiresAt,
        publishedAt: row.publishedAt,
      },
      row.createdAt,
      row.updatedAt,
    );
  }

  private toListItem(row: RawTransferListingWithCount): TransferListingListItem {
    return {
      id: row.id,
      sellerId: row.sellerId,
      category: row.category as TransferListingListItem['category'],
      status: row.status as TransferListingListItem['status'],
      title: row.title,
      address: row.address,
      district: row.district,
      city: row.city,
      latitude: Number(row.lat),
      longitude: Number(row.lng),
      askingPriceVND: row.askingPriceVND,
      aiEstimatePriceVND: row.aiEstimatePriceVND,
      pricingSource: row.pricingSource as TransferListingListItem['pricingSource'],
      isNegotiable: row.isNegotiable,
      areaM2: row.areaM2,
      media: row.media as Record<string, unknown>[] | null,
      viewCount: row.viewCount,
      inquiryCount: row.inquiryCount,
      publishedAt: row.publishedAt,
      itemCount: row.itemCount,
    };
  }

  private toDetail(row: RawTransferListing, items: RawTransferItem[]): TransferListingDetailData {
    return {
      id: row.id,
      sellerId: row.sellerId,
      category: row.category as TransferListingDetailData['category'],
      status: row.status as TransferListingDetailData['status'],
      title: row.title,
      description: row.description,
      address: row.address,
      ward: row.ward,
      district: row.district,
      city: row.city,
      latitude: Number(row.lat),
      longitude: Number(row.lng),
      askingPriceVND: row.askingPriceVND,
      aiEstimatePriceVND: row.aiEstimatePriceVND,
      aiConfidence: row.aiConfidence,
      pricingSource: row.pricingSource as TransferListingDetailData['pricingSource'],
      isNegotiable: row.isNegotiable,
      areaM2: row.areaM2,
      monthlyRentVND: row.monthlyRentVND,
      depositMonths: row.depositMonths,
      remainingLeaseMo: row.remainingLeaseMo,
      businessType: row.businessType,
      footTraffic: row.footTraffic,
      media: row.media as Record<string, unknown>[] | null,
      moderationScore: row.moderationScore,
      moderationNotes: row.moderationNotes,
      viewCount: row.viewCount,
      saveCount: row.saveCount,
      inquiryCount: row.inquiryCount,
      contactPhone: row.contactPhone,
      contactName: row.contactName,
      featuredUntil: row.featuredUntil,
      expiresAt: row.expiresAt,
      publishedAt: row.publishedAt,
      items: items.map((i) => this.toItemData(i)),
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    };
  }

  private toItemData(row: RawTransferItem): TransferItemData {
    return {
      id: row.id,
      name: row.name,
      brand: row.brand,
      modelName: row.modelName,
      category: row.category as TransferItemData['category'],
      condition: row.condition as TransferItemData['condition'],
      purchaseYear: row.purchaseYear,
      originalPriceVND: row.originalPriceVND,
      askingPriceVND: row.askingPriceVND,
      aiEstimatePriceVND: row.aiEstimatePriceVND,
      aiConfidence: row.aiConfidence,
      quantity: row.quantity,
      dimensions: row.dimensions as Record<string, unknown> | null,
      media: row.media as Record<string, unknown>[] | null,
      notes: row.notes,
    };
  }
}
