import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { and, arrayContains, asc, count, desc, eq, gte, inArray, lte, or, SQL, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { ZodError } from 'zod';
import * as schema from '../drizzle/schema';
import { businesses_json, PG_CONNECTION } from '../drizzle/schema';
import { GeoService } from '../geo/geo.service';
import { BusinessListing, BusinessListingSchema } from '../models/db.model';
import { BusinessListingCriteria, JwtUser } from '../models/main.model';
import { getDistanceQuery, splitName } from '../utils';
import { generateSlug, extractShortIdFromSlug, isSlug } from '../utils/slug.utils';

@Injectable()
export class BusinessListingService {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
    @Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
    private geoService?: GeoService,
  ) { }

  private getWhereConditions(criteria: BusinessListingCriteria, user: JwtUser): SQL[] {
    const whereConditions: SQL[] = [];
    this.logger.info('getWhereConditions start', { criteria: JSON.stringify(criteria) });

    if (criteria.city && criteria.searchType === 'exact') {
      whereConditions.push(sql`(${businesses_json.data}->'location'->>'name') ILIKE ${criteria.city.name}`);
    }

    if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
      this.logger.debug('Adding radius search filter', { city: criteria.city.name, radius: criteria.radius });
      const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name);
      whereConditions.push(sql`(${getDistanceQuery(businesses_json, cityGeo.latitude, cityGeo.longitude)} <= ${criteria.radius})`);
    }
    if (criteria.types && criteria.types.length > 0) {
      this.logger.warn('Adding business category filter', { types: criteria.types });
      // Use explicit SQL with IN for robust JSONB comparison
      const typeValues = criteria.types.map(t => sql`${t}`);
      whereConditions.push(sql`((${businesses_json.data}->>'type') IN (${sql.join(typeValues, sql`, `)}))`);
    }

    if (criteria.state) {
      this.logger.debug('Adding state filter', { state: criteria.state });
      whereConditions.push(sql`(${businesses_json.data}->'location'->>'state') = ${criteria.state}`);
    }

    if (criteria.minPrice !== undefined && criteria.minPrice !== null) {
      whereConditions.push(
        and(
          sql`(${businesses_json.data}->>'price') IS NOT NULL`,
          sql`(${businesses_json.data}->>'price') != ''`,
          gte(sql`REPLACE(${businesses_json.data}->>'price', ',', '')::double precision`, criteria.minPrice)
        )
      );
    }

    if (criteria.maxPrice !== undefined && criteria.maxPrice !== null) {
      whereConditions.push(
        and(
          sql`(${businesses_json.data}->>'price') IS NOT NULL`,
          sql`(${businesses_json.data}->>'price') != ''`,
          lte(sql`REPLACE(${businesses_json.data}->>'price', ',', '')::double precision`, criteria.maxPrice)
        )
      );
    }

    if (criteria.minRevenue) {
      whereConditions.push(gte(sql`(${businesses_json.data}->>'salesRevenue')::double precision`, criteria.minRevenue));
    }

    if (criteria.maxRevenue) {
      whereConditions.push(lte(sql`(${businesses_json.data}->>'salesRevenue')::double precision`, criteria.maxRevenue));
    }

    if (criteria.minCashFlow) {
      whereConditions.push(gte(sql`(${businesses_json.data}->>'cashFlow')::double precision`, criteria.minCashFlow));
    }

    if (criteria.maxCashFlow) {
      whereConditions.push(lte(sql`(${businesses_json.data}->>'cashFlow')::double precision`, criteria.maxCashFlow));
    }

    if (criteria.minNumberEmployees) {
      whereConditions.push(gte(sql`(${businesses_json.data}->>'employees')::integer`, criteria.minNumberEmployees));
    }

    if (criteria.maxNumberEmployees) {
      whereConditions.push(lte(sql`(${businesses_json.data}->>'employees')::integer`, criteria.maxNumberEmployees));
    }

    if (criteria.establishedMin) {
      whereConditions.push(gte(sql`(${businesses_json.data}->>'established')::integer`, criteria.establishedMin));
    }

    if (criteria.realEstateChecked) {
      whereConditions.push(eq(sql`(${businesses_json.data}->>'realEstateIncluded')::boolean`, criteria.realEstateChecked));
    }

    if (criteria.leasedLocation) {
      whereConditions.push(eq(sql`(${businesses_json.data}->>'leasedLocation')::boolean`, criteria.leasedLocation));
    }

    if (criteria.franchiseResale) {
      whereConditions.push(eq(sql`(${businesses_json.data}->>'franchiseResale')::boolean`, criteria.franchiseResale));
    }

    if (criteria.title && criteria.title.trim() !== '') {
      const searchTerm = `%${criteria.title.trim()}%`;
      whereConditions.push(
        sql`((${businesses_json.data}->>'title') ILIKE ${searchTerm} OR (${businesses_json.data}->>'description') ILIKE ${searchTerm})`
      );
    }
    if (criteria.brokerName) {
      const { firstname, lastname } = splitName(criteria.brokerName);
      if (firstname === lastname) {
        whereConditions.push(
          sql`((${schema.users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${schema.users_json.data}->>'lastname') ILIKE ${`%${lastname}%`})`
        );
      } else {
        whereConditions.push(
          sql`((${schema.users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} AND (${schema.users_json.data}->>'lastname') ILIKE ${`%${lastname}%`})`
        );
      }
    }
    if (criteria.email) {
      whereConditions.push(eq(schema.users_json.email, criteria.email));
    }
    if (user?.role !== 'admin') {
      whereConditions.push(
        sql`((${businesses_json.email} = ${user?.email || null}) OR (${businesses_json.data}->>'draft')::boolean IS NOT TRUE)`
      );
    }
    this.logger.warn('whereConditions count', { count: whereConditions.length });
    return whereConditions;
  }

  async searchBusinessListings(criteria: BusinessListingCriteria, user: JwtUser) {
    const start = criteria.start ? criteria.start : 0;
    const length = criteria.length ? criteria.length : 12;
    const query = this.conn
      .select({
        business: businesses_json,
        brokerFirstName: sql`${schema.users_json.data}->>'firstname'`.as('brokerFirstName'),
        brokerLastName: sql`${schema.users_json.data}->>'lastname'`.as('brokerLastName'),
      })
      .from(businesses_json)
      .leftJoin(schema.users_json, eq(businesses_json.email, schema.users_json.email));

    const whereConditions = this.getWhereConditions(criteria, user);

    this.logger.warn('Filter Criteria:', { criteria: JSON.stringify(criteria) });

    if (whereConditions.length > 0) {
      const whereClause = sql.join(whereConditions, sql` AND `);
      query.where(sql`(${whereClause})`);

      this.logger.warn('Generated SQL:', { sql: query.toSQL().sql, params: query.toSQL().params });
    }

    // Sortierung
    switch (criteria.sortBy) {
      case 'priceAsc':
        query.orderBy(asc(sql`(${businesses_json.data}->>'price')::double precision`));
        break;
      case 'priceDesc':
        query.orderBy(desc(sql`(${businesses_json.data}->>'price')::double precision`));
        break;
      case 'srAsc':
        query.orderBy(asc(sql`(${businesses_json.data}->>'salesRevenue')::double precision`));
        break;
      case 'srDesc':
        query.orderBy(desc(sql`(${businesses_json.data}->>'salesRevenue')::double precision`));
        break;
      case 'cfAsc':
        query.orderBy(asc(sql`(${businesses_json.data}->>'cashFlow')::double precision`));
        break;
      case 'cfDesc':
        query.orderBy(desc(sql`(${businesses_json.data}->>'cashFlow')::double precision`));
        break;
      case 'creationDateFirst':
        query.orderBy(asc(sql`${businesses_json.data}->>'created'`));
        break;
      case 'creationDateLast':
        query.orderBy(desc(sql`${businesses_json.data}->>'created'`));
        break;
      default: {
        // NEU (created < 14 Tage) > UPDATED (updated < 14 Tage) > Rest
        const recencyRank = sql`
      CASE
        WHEN ((${businesses_json.data}->>'created')::timestamptz >= (now() - interval '14 days')) THEN 2
        WHEN ((${businesses_json.data}->>'updated')::timestamptz >= (now() - interval '14 days')) THEN 1
        ELSE 0
      END
    `;

        // Innerhalb der Gruppe:
        // NEW  → created DESC
        // UPDATED → updated DESC
        // Rest → created DESC
        const groupTimestamp = sql`
      CASE
        WHEN ((${businesses_json.data}->>'created')::timestamptz >= (now() - interval '14 days'))
          THEN (${businesses_json.data}->>'created')::timestamptz
        WHEN ((${businesses_json.data}->>'updated')::timestamptz >= (now() - interval '14 days'))
          THEN (${businesses_json.data}->>'updated')::timestamptz
        ELSE (${businesses_json.data}->>'created')::timestamptz
      END
    `;

        query.orderBy(desc(recencyRank), desc(groupTimestamp), desc(sql`(${businesses_json.data}->>'created')::timestamptz`));
        break;
      }
    }
    // Paginierung
    query.limit(length).offset(start);

    const data = await query;
    const totalCount = await this.getBusinessListingsCount(criteria, user);
    const results = data.map(r => ({
      id: r.business.id,
      email: r.business.email,
      ...(r.business.data as BusinessListing),
      brokerFirstName: r.brokerFirstName,
      brokerLastName: r.brokerLastName,
    }));
    return {
      results,
      totalCount,
    };
  }

  async getBusinessListingsCount(criteria: BusinessListingCriteria, user: JwtUser): Promise<number> {
    const countQuery = this.conn.select({ value: count() }).from(businesses_json).leftJoin(schema.users_json, eq(businesses_json.email, schema.users_json.email));

    const whereConditions = this.getWhereConditions(criteria, user);

    if (whereConditions.length > 0) {
      const whereClause = sql.join(whereConditions, sql` AND `);
      countQuery.where(sql`(${whereClause})`);
    }

    const [{ value: totalCount }] = await countQuery;
    return totalCount;
  }

  /**
   * Find business by slug or ID
   * Supports both slug (e.g., "restaurant-austin-tx-a3f7b2c1") and UUID
   */
  async findBusinessBySlugOrId(slugOrId: string, user: JwtUser): Promise<BusinessListing> {
    this.logger.debug(`findBusinessBySlugOrId called with: ${slugOrId}`);

    let id = slugOrId;

    // Check if it's a slug (contains multiple hyphens) vs UUID
    if (isSlug(slugOrId)) {
      this.logger.debug(`Detected as slug: ${slugOrId}`);

      // Extract short ID from slug and find by slug field
      const listing = await this.findBusinessBySlug(slugOrId);
      if (listing) {
        this.logger.debug(`Found listing by slug: ${slugOrId} -> ID: ${listing.id}`);
        id = listing.id;
      } else {
        this.logger.warn(`Slug not found in database: ${slugOrId}`);
        throw new NotFoundException(
          `Business listing not found with slug: ${slugOrId}. ` +
          `The listing may have been deleted or the URL may be incorrect.`
        );
      }
    } else {
      this.logger.debug(`Detected as UUID: ${slugOrId}`);
    }

    return this.findBusinessesById(id, user);
  }

  /**
   * Find business by slug
   */
  async findBusinessBySlug(slug: string): Promise<BusinessListing | null> {
    const result = await this.conn
      .select()
      .from(businesses_json)
      .where(sql`${businesses_json.data}->>'slug' = ${slug}`)
      .limit(1);

    if (result.length > 0) {
      return { id: result[0].id, email: result[0].email, ...(result[0].data as BusinessListing) } as BusinessListing;
    }
    return null;
  }

  async findBusinessesById(id: string, user: JwtUser): Promise<BusinessListing> {
    const conditions = [];
    if (user?.role !== 'admin') {
      conditions.push(or(eq(businesses_json.email, user?.email), sql`(${businesses_json.data}->>'draft')::boolean IS NOT TRUE`));
    }
    conditions.push(eq(businesses_json.id, id));
    const result = await this.conn
      .select()
      .from(businesses_json)
      .where(and(...conditions));
    if (result.length > 0) {
      return { id: result[0].id, email: result[0].email, ...(result[0].data as BusinessListing) } as BusinessListing;
    } else {
      throw new BadRequestException(`No entry available for ${id}`);
    }
  }

  async findBusinessesByEmail(email: string, user: JwtUser): Promise<BusinessListing[]> {
    const conditions = [];
    conditions.push(eq(businesses_json.email, email));
    if (email !== user?.email && user?.role !== 'admin') {
      conditions.push(sql`(${businesses_json.data}->>'draft')::boolean IS NOT TRUE`);
    }
    const listings = await this.conn
      .select()
      .from(businesses_json)
      .where(and(...conditions));
    return listings.map(l => ({ id: l.id, email: l.email, ...(l.data as BusinessListing) }) as BusinessListing);
  }

  async findFavoriteListings(user: JwtUser): Promise<BusinessListing[]> {
    const userFavorites = await this.conn
      .select()
      .from(businesses_json)
      .where(sql`${businesses_json.data}->'favoritesForUser' ? ${user.email}`);
    return userFavorites.map(l => ({ id: l.id, email: l.email, ...(l.data as BusinessListing) }) as BusinessListing);
  }

  async createListing(data: BusinessListing): Promise<BusinessListing> {
    try {
      data.created = data.created ? (typeof data.created === 'string' ? new Date(data.created) : data.created) : new Date();
      data.updated = new Date();
      BusinessListingSchema.parse(data);
      const { id, email, ...rest } = data;
      const convertedBusinessListing = { email, data: rest };
      const [createdListing] = await this.conn.insert(businesses_json).values(convertedBusinessListing).returning();

      // Generate and update slug after creation (we need the ID first)
      const slug = generateSlug(data.title, data.location, createdListing.id);
      const listingWithSlug = { ...(createdListing.data as any), slug };
      await this.conn.update(businesses_json).set({ data: listingWithSlug }).where(eq(businesses_json.id, createdListing.id));

      return { id: createdListing.id, email: createdListing.email, ...(createdListing.data as BusinessListing), slug } as any;
    } catch (error) {
      if (error instanceof ZodError) {
        const filteredErrors = error.errors
          .map(item => ({
            ...item,
            field: item.path[0],
          }))
          .filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
        throw new BadRequestException(filteredErrors);
      }
      throw error;
    }
  }

  async updateBusinessListing(id: string, data: BusinessListing, user: JwtUser): Promise<BusinessListing> {
    try {
      const [existingListing] = await this.conn.select().from(businesses_json).where(eq(businesses_json.id, id));

      if (!existingListing) {
        throw new NotFoundException(`Business listing with id ${id} not found`);
      }
      data.updated = new Date();
      data.created = data.created ? (typeof data.created === 'string' ? new Date(data.created) : data.created) : new Date();
      if (existingListing.email === user?.email) {
        data.favoritesForUser = (<BusinessListing>existingListing.data).favoritesForUser || [];
      }

      // Regenerate slug if title or location changed
      const existingData = existingListing.data as BusinessListing;
      let slug: string;
      if (data.title !== existingData.title || JSON.stringify(data.location) !== JSON.stringify(existingData.location)) {
        slug = generateSlug(data.title, data.location, id);
      } else {
        // Keep existing slug
        slug = (existingData as any).slug || generateSlug(data.title, data.location, id);
      }

      // Add slug to data before validation
      const dataWithSlug = { ...data, slug };
      BusinessListingSchema.parse(dataWithSlug);
      const { id: _, email, ...rest } = dataWithSlug;
      const convertedBusinessListing = { email, data: rest };
      const [updateListing] = await this.conn.update(businesses_json).set(convertedBusinessListing).where(eq(businesses_json.id, id)).returning();
      return { id: updateListing.id, email: updateListing.email, ...(updateListing.data as BusinessListing) };
    } catch (error) {
      if (error instanceof ZodError) {
        const filteredErrors = error.errors
          .map(item => ({
            ...item,
            field: item.path[0],
          }))
          .filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
        throw new BadRequestException(filteredErrors);
      }
      throw error;
    }
  }

  async deleteListing(id: string): Promise<void> {
    await this.conn.delete(businesses_json).where(eq(businesses_json.id, id));
  }

  async addFavorite(id: string, user: JwtUser): Promise<void> {
    await this.conn
      .update(businesses_json)
      .set({
        data: sql`jsonb_set(${businesses_json.data}, '{favoritesForUser}',
          coalesce((${businesses_json.data}->'favoritesForUser')::jsonb, '[]'::jsonb) || to_jsonb(${user.email}::text))`,
      })
      .where(eq(businesses_json.id, id));
  }

  async deleteFavorite(id: string, user: JwtUser): Promise<void> {
    await this.conn
      .update(businesses_json)
      .set({
        data: sql`jsonb_set(${businesses_json.data}, '{favoritesForUser}',
          (SELECT coalesce(jsonb_agg(elem), '[]'::jsonb)
           FROM jsonb_array_elements(coalesce(${businesses_json.data}->'favoritesForUser', '[]'::jsonb)) AS elem
           WHERE elem::text != to_jsonb(${user.email}::text)::text))`,
      })
      .where(eq(businesses_json.id, id));
  }
}
