import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { ProjectDevelopmentEntity } from '../../domain/entities/project-development.entity';
import type {
  IProjectRepository,
  ProjectSearchParams,
  PaginatedResult,
  ProjectListItem,
  ProjectDetailData,
} from '../../domain/repositories/project-development.repository';

@Injectable()
export class PrismaProjectDevelopmentRepository implements IProjectRepository {
  constructor(private readonly prisma: PrismaService) {}

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

  async findBySlug(slug: string): Promise<ProjectDevelopmentEntity | null> {
    const row = await this.prisma.$queryRaw<RawProject[]>`
      SELECT *, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng
      FROM "ProjectDevelopment" WHERE slug = ${slug} LIMIT 1
    `;
    return row[0] ? this.toDomain(row[0]) : null;
  }

  async findDetailBySlug(slug: string): Promise<ProjectDetailData | null> {
    const rows = await this.prisma.$queryRaw<RawProjectDetail[]>`
      SELECT p.*,
        ST_Y(p.location::geometry) as lat,
        ST_X(p.location::geometry) as lng,
        COUNT(pr.id)::int as "propertyCount"
      FROM "ProjectDevelopment" p
      LEFT JOIN "Property" pr ON pr."projectDevelopmentId" = p.id
      WHERE p.slug = ${slug}
      GROUP BY p.id
      LIMIT 1
    `;
    return rows[0] ? this.toDetail(rows[0]) : null;
  }

  async findDetailById(id: string): Promise<ProjectDetailData | null> {
    const rows = await this.prisma.$queryRaw<RawProjectDetail[]>`
      SELECT p.*,
        ST_Y(p.location::geometry) as lat,
        ST_X(p.location::geometry) as lng,
        COUNT(pr.id)::int as "propertyCount"
      FROM "ProjectDevelopment" p
      LEFT JOIN "Property" pr ON pr."projectDevelopmentId" = p.id
      WHERE p.id = ${id}
      GROUP BY p.id
      LIMIT 1
    `;
    return rows[0] ? this.toDetail(rows[0]) : null;
  }

  async save(entity: ProjectDevelopmentEntity): Promise<void> {
    await this.prisma.$executeRaw`
      INSERT INTO "ProjectDevelopment" (
        id, name, slug, developer, "developerLogo", "totalUnits", "completedUnits",
        status, "startDate", "completionDate", description, amenities, "masterPlanUrl",
        location, address, ward, district, city,
        "minPrice", "maxPrice", "pricePerM2Range", "totalArea",
        "buildingCount", "floorCount", "unitTypes", media, documents,
        tags, "suitableFor", "whyThisLocation", "isVerified", "ownerId",
        "createdAt", "updatedAt"
      ) VALUES (
        ${entity.id}, ${entity.name}, ${entity.slug}, ${entity.developer},
        ${entity.developerLogo}, ${entity.totalUnits}, ${entity.completedUnits},
        ${entity.status}::"ProjectDevelopmentStatus",
        ${entity.startDate}, ${entity.completionDate},
        ${entity.description},
        ${entity.amenities ? JSON.stringify(entity.amenities) : null}::jsonb,
        ${entity.masterPlanUrl},
        ST_SetSRID(ST_MakePoint(${entity.longitude}, ${entity.latitude}), 4326),
        ${entity.address}, ${entity.ward}, ${entity.district}, ${entity.city},
        ${entity.minPrice}, ${entity.maxPrice},
        ${entity.pricePerM2Range ? JSON.stringify(entity.pricePerM2Range) : null}::jsonb,
        ${entity.totalArea}, ${entity.buildingCount}, ${entity.floorCount},
        ${entity.unitTypes ? JSON.stringify(entity.unitTypes) : null}::jsonb,
        ${entity.media ? JSON.stringify(entity.media) : null}::jsonb,
        ${entity.documents ? JSON.stringify(entity.documents) : null}::jsonb,
        ${entity.tags}::text[],
        ${entity.suitableFor}::text[],
        ${entity.whyThisLocation},
        ${entity.isVerified}, ${entity.ownerId},
        ${entity.createdAt}, ${entity.updatedAt}
      )
    `;
  }

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

  async update(entity: ProjectDevelopmentEntity): Promise<void> {
    await this.prisma.$executeRaw`
      UPDATE "ProjectDevelopment" SET
        name = ${entity.name}, developer = ${entity.developer},
        "developerLogo" = ${entity.developerLogo},
        "totalUnits" = ${entity.totalUnits}, "completedUnits" = ${entity.completedUnits},
        status = ${entity.status}::"ProjectDevelopmentStatus",
        "startDate" = ${entity.startDate}, "completionDate" = ${entity.completionDate},
        description = ${entity.description},
        amenities = ${entity.amenities ? JSON.stringify(entity.amenities) : null}::jsonb,
        "masterPlanUrl" = ${entity.masterPlanUrl},
        "minPrice" = ${entity.minPrice}, "maxPrice" = ${entity.maxPrice},
        "pricePerM2Range" = ${entity.pricePerM2Range ? JSON.stringify(entity.pricePerM2Range) : null}::jsonb,
        "totalArea" = ${entity.totalArea},
        "buildingCount" = ${entity.buildingCount}, "floorCount" = ${entity.floorCount},
        "unitTypes" = ${entity.unitTypes ? JSON.stringify(entity.unitTypes) : null}::jsonb,
        media = ${entity.media ? JSON.stringify(entity.media) : null}::jsonb,
        documents = ${entity.documents ? JSON.stringify(entity.documents) : null}::jsonb,
        tags = ${entity.tags}::text[],
        "suitableFor" = ${entity.suitableFor}::text[],
        "whyThisLocation" = ${entity.whyThisLocation},
        "isVerified" = ${entity.isVerified},
        "ownerId" = ${entity.ownerId},
        "updatedAt" = ${entity.updatedAt}
      WHERE id = ${entity.id}
    `;
  }

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

    const clauses: Prisma.Sql[] = [Prisma.sql`1=1`];

    if (params.status) {
      clauses.push(Prisma.sql`status = ${params.status}::"ProjectDevelopmentStatus"`);
    }
    if (params.city) {
      clauses.push(Prisma.sql`city = ${params.city}`);
    }
    if (params.district) {
      clauses.push(Prisma.sql`district = ${params.district}`);
    }
    if (params.developer) {
      clauses.push(Prisma.sql`developer ILIKE ${'%' + params.developer + '%'}`);
    }
    if (params.isVerified !== undefined) {
      clauses.push(Prisma.sql`"isVerified" = ${params.isVerified}`);
    }
    if (params.ownerId) {
      clauses.push(Prisma.sql`"ownerId" = ${params.ownerId}`);
    }
    if (params.query) {
      const like = `%${params.query}%`;
      clauses.push(Prisma.sql`(name ILIKE ${like} OR developer ILIKE ${like} OR district ILIKE ${like} OR city ILIKE ${like})`);
    }

    const where = Prisma.join(clauses, ' AND ');

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

    const rows = await this.prisma.$queryRaw<RawProjectDetail[]>(
      Prisma.sql`
        SELECT p.*, ST_Y(p.location::geometry) as lat, ST_X(p.location::geometry) as lng,
          COUNT(pr.id)::int as "propertyCount"
        FROM "ProjectDevelopment" p
        LEFT JOIN "Property" pr ON pr."projectDevelopmentId" = p.id
        WHERE ${where}
        GROUP BY p.id
        ORDER BY p."createdAt" DESC
        LIMIT ${limit} OFFSET ${offset}
      `,
    );

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

  private toDomain(row: RawProject): ProjectDevelopmentEntity {
    return new ProjectDevelopmentEntity(
      row.id,
      {
        name: row.name,
        slug: row.slug,
        developer: row.developer,
        developerLogo: row.developerLogo,
        totalUnits: row.totalUnits,
        completedUnits: row.completedUnits,
        status: row.status,
        startDate: row.startDate,
        completionDate: row.completionDate,
        description: row.description,
        amenities: row.amenities as Record<string, unknown> | null,
        masterPlanUrl: row.masterPlanUrl,
        latitude: Number(row.lat),
        longitude: Number(row.lng),
        address: row.address,
        ward: row.ward,
        district: row.district,
        city: row.city,
        minPrice: row.minPrice,
        maxPrice: row.maxPrice,
        pricePerM2Range: row.pricePerM2Range as Record<string, unknown> | null,
        totalArea: row.totalArea,
        buildingCount: row.buildingCount,
        floorCount: row.floorCount,
        unitTypes: row.unitTypes as Record<string, unknown> | null,
        media: row.media as Record<string, unknown>[] | null,
        documents: row.documents as Record<string, unknown>[] | null,
        tags: row.tags ?? [],
        suitableFor: row.suitableFor ?? [],
        whyThisLocation: row.whyThisLocation,
        isVerified: row.isVerified,
        ownerId: row.ownerId,
      },
      row.createdAt,
      row.updatedAt,
    );
  }

  private toListItem(row: RawProjectDetail): ProjectListItem {
    return {
      id: row.id,
      name: row.name,
      slug: row.slug,
      developer: row.developer,
      developerLogo: row.developerLogo,
      status: row.status,
      totalUnits: row.totalUnits,
      completedUnits: row.completedUnits,
      address: row.address,
      ward: row.ward,
      district: row.district,
      city: row.city,
      minPrice: row.minPrice,
      maxPrice: row.maxPrice,
      totalArea: row.totalArea,
      tags: row.tags ?? [],
      suitableFor: row.suitableFor ?? [],
      whyThisLocation: row.whyThisLocation,
      isVerified: row.isVerified,
      ownerId: row.ownerId,
      latitude: Number(row.lat),
      longitude: Number(row.lng),
      propertyCount: row.propertyCount ?? 0,
      createdAt: row.createdAt,
    };
  }

  private toDetail(row: RawProjectDetail): ProjectDetailData {
    return {
      ...this.toListItem(row),
      startDate: row.startDate,
      completionDate: row.completionDate,
      description: row.description,
      amenities: row.amenities as Record<string, unknown> | null,
      masterPlanUrl: row.masterPlanUrl,
      pricePerM2Range: row.pricePerM2Range as Record<string, unknown> | null,
      buildingCount: row.buildingCount,
      floorCount: row.floorCount,
      unitTypes: row.unitTypes as Record<string, unknown> | null,
      media: row.media as Record<string, unknown>[] | null,
      documents: row.documents as Record<string, unknown>[] | null,
      updatedAt: row.updatedAt,
    };
  }
}

interface RawProject {
  id: string;
  name: string;
  slug: string;
  developer: string;
  developerLogo: string | null;
  totalUnits: number;
  completedUnits: number;
  status: 'PLANNING' | 'UNDER_CONSTRUCTION' | 'COMPLETED' | 'HANDOVER';
  startDate: Date | null;
  completionDate: Date | null;
  description: string | null;
  amenities: Prisma.JsonValue;
  masterPlanUrl: string | null;
  lat: number;
  lng: number;
  address: string;
  ward: string;
  district: string;
  city: string;
  minPrice: bigint | null;
  maxPrice: bigint | null;
  pricePerM2Range: Prisma.JsonValue;
  totalArea: number | null;
  buildingCount: number | null;
  floorCount: number | null;
  unitTypes: Prisma.JsonValue;
  media: Prisma.JsonValue;
  documents: Prisma.JsonValue;
  tags: string[] | null;
  suitableFor: string[] | null;
  whyThisLocation: string | null;
  isVerified: boolean;
  ownerId: string | null;
  createdAt: Date;
  updatedAt: Date;
}

interface RawProjectDetail extends RawProject {
  propertyCount: number;
}
