import { Injectable } from '@nestjs/common';
import { Prisma, Property as PrismaProperty, PropertyMedia as PrismaMedia } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { PropertyMediaEntity, PropertyMediaProps } from '../../domain/entities/property-media.entity';
import { PropertyEntity, PropertyProps } from '../../domain/entities/property.entity';
import { IPropertyRepository } from '../../domain/repositories/property.repository';
import { Address } from '../../domain/value-objects/address.vo';
import { GeoPoint } from '../../domain/value-objects/geo-point.vo';

@Injectable()
export class PrismaPropertyRepository implements IPropertyRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string): Promise<PropertyEntity | null> {
    const property = await this.prisma.property.findUnique({ where: { id } });
    return property ? this.toDomain(property) : null;
  }

  async save(entity: PropertyEntity): Promise<void> {
    await this.prisma.$executeRaw`
      INSERT INTO "Property" (
        "id", "propertyType", "title", "description", "address", "ward", "district", "city",
        "location", "areaM2", "usableAreaM2", "bedrooms", "bathrooms", "floors", "floor",
        "totalFloors", "direction", "yearBuilt", "legalStatus", "amenities", "nearbyPOIs",
        "metroDistanceM", "projectName", "createdAt", "updatedAt"
      ) VALUES (
        ${entity.id}, ${entity.propertyType}::"PropertyType", ${entity.title}, ${entity.description},
        ${entity.address.address}, ${entity.address.ward}, ${entity.address.district}, ${entity.address.city},
        ST_SetSRID(ST_MakePoint(${entity.location.longitude}, ${entity.location.latitude}), 4326),
        ${entity.areaM2}, ${entity.usableAreaM2}, ${entity.bedrooms}, ${entity.bathrooms},
        ${entity.floors}, ${entity.floor}, ${entity.totalFloors},
        ${entity.direction}::"Direction", ${entity.yearBuilt}, ${entity.legalStatus},
        ${entity.amenities ? JSON.stringify(entity.amenities) : null}::jsonb,
        ${entity.nearbyPOIs ? JSON.stringify(entity.nearbyPOIs) : null}::jsonb,
        ${entity.metroDistanceM}, ${entity.projectName},
        ${entity.createdAt}, ${entity.updatedAt}
      )`;
  }

  async update(entity: PropertyEntity): Promise<void> {
    await this.prisma.$executeRaw`
      UPDATE "Property" SET
        "propertyType" = ${entity.propertyType}::"PropertyType",
        "title" = ${entity.title},
        "description" = ${entity.description},
        "address" = ${entity.address.address},
        "ward" = ${entity.address.ward},
        "district" = ${entity.address.district},
        "city" = ${entity.address.city},
        "location" = ST_SetSRID(ST_MakePoint(${entity.location.longitude}, ${entity.location.latitude}), 4326),
        "areaM2" = ${entity.areaM2},
        "usableAreaM2" = ${entity.usableAreaM2},
        "bedrooms" = ${entity.bedrooms},
        "bathrooms" = ${entity.bathrooms},
        "direction" = ${entity.direction}::"Direction",
        "yearBuilt" = ${entity.yearBuilt},
        "legalStatus" = ${entity.legalStatus},
        "projectName" = ${entity.projectName},
        "updatedAt" = NOW()
      WHERE "id" = ${entity.id}`;
  }

  async addMedia(media: PropertyMediaEntity): Promise<void> {
    await this.prisma.propertyMedia.create({
      data: {
        id: media.id,
        propertyId: media.propertyId,
        url: media.url,
        type: media.type,
        order: media.order,
        caption: media.caption,
        aiTags: media.aiTags as Prisma.InputJsonValue,
      },
    });
  }

  async findMediaByPropertyId(propertyId: string): Promise<PropertyMediaEntity[]> {
    const mediaList = await this.prisma.propertyMedia.findMany({
      where: { propertyId },
      orderBy: { order: 'asc' },
      take: 50,
    });
    return mediaList.map((m) => this.toMediaDomain(m));
  }

  async deleteMedia(mediaId: string): Promise<void> {
    await this.prisma.propertyMedia.delete({ where: { id: mediaId } });
  }

  async countMediaByPropertyId(propertyId: string): Promise<number> {
    return this.prisma.propertyMedia.count({ where: { propertyId } });
  }

  private toDomain(raw: PrismaProperty): PropertyEntity {
    // PostGIS geometry is returned as a raw object — extract lat/lng
    // For raw SQL results we'd get WKB, but Prisma returns Unsupported as-is
    const geoPoint = GeoPoint.create(0, 0).unwrap(); // placeholder — location read via raw SQL
    const address = Address.create(raw.address, raw.ward, raw.district, raw.city).unwrap();

    const props: PropertyProps = {
      propertyType: raw.propertyType,
      title: raw.title,
      description: raw.description,
      address,
      location: geoPoint,
      areaM2: raw.areaM2,
      usableAreaM2: raw.usableAreaM2,
      bedrooms: raw.bedrooms,
      bathrooms: raw.bathrooms,
      floors: raw.floors,
      floor: raw.floor,
      totalFloors: raw.totalFloors,
      direction: raw.direction,
      yearBuilt: raw.yearBuilt,
      legalStatus: raw.legalStatus,
      amenities: raw.amenities,
      nearbyPOIs: raw.nearbyPOIs,
      metroDistanceM: raw.metroDistanceM,
      projectName: raw.projectName,
    };

    return new PropertyEntity(raw.id, props, raw.createdAt, raw.updatedAt);
  }

  private toMediaDomain(raw: PrismaMedia): PropertyMediaEntity {
    const props: PropertyMediaProps = {
      propertyId: raw.propertyId,
      url: raw.url,
      type: raw.type as 'image' | 'video',
      order: raw.order,
      caption: raw.caption,
      aiTags: raw.aiTags,
    };
    return new PropertyMediaEntity(raw.id, props, raw.createdAt);
  }
}
