import { Prisma } from '@prisma/client';
import { prisma } from '../../../config/database';
import { logger } from '../../../utils/logger';
import { AppError } from '../../../middleware/error-handler';
import { representApiClient, type RepresentRepresentative } from './represent-api.client';
import { postalCodesService } from '../postal-codes/postal-codes.service';
import type { ListRepresentativesInput } from './representatives.schemas';

function deduplicateReps(reps: RepresentRepresentative[]): RepresentRepresentative[] {
  const seen = new Set<string>();
  return reps.filter((rep) => {
    const key = `${rep.name}|${rep.elected_office}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

export const representativesService = {
  async lookupByPostalCode(code: string, forceRefresh = false) {
    // 1. Check cache unless forcing refresh
    if (!forceRefresh) {
      const cached = await prisma.representative.findMany({
        where: { postalCode: code },
      });

      if (cached.length > 0) {
        const postalInfo = await postalCodesService.findByPostalCode(code);
        return {
          source: 'cache' as const,
          postalCode: code,
          location: {
            city: postalInfo?.city ?? null,
            province: postalInfo?.province ?? null,
          },
          representatives: cached,
        };
      }
    }

    // 2. Call Represent API
    const apiResponse = await representApiClient.getByPostalCode(code);

    // Merge centroid + concordance reps and deduplicate
    const allReps = [
      ...(apiResponse.representatives_centroid || []),
      ...(apiResponse.representatives_concordance || []),
    ];
    const uniqueReps = deduplicateReps(allReps);

    // 3. Fire-and-forget cache write
    const cacheWrite = async () => {
      try {
        // Delete old cached reps for this postal code
        await prisma.representative.deleteMany({ where: { postalCode: code } });

        // Cache new reps
        if (uniqueReps.length > 0) {
          await prisma.representative.createMany({
            data: uniqueReps.map((rep) => ({
              postalCode: code,
              name: rep.name || null,
              email: rep.email || null,
              districtName: rep.district_name || null,
              electedOffice: rep.elected_office || null,
              partyName: rep.party_name || null,
              representativeSetName: rep.representative_set_name || null,
              url: rep.url || null,
              photoUrl: rep.photo_url || null,
              offices: rep.offices ? (rep.offices as unknown as Prisma.InputJsonValue) : Prisma.JsonNull,
            })),
          });
        }

        // Upsert postal code cache
        const coords = apiResponse.centroid?.coordinates;
        await postalCodesService.upsert({
          postalCode: code,
          city: apiResponse.city,
          province: apiResponse.province,
          centroidLat: coords ? coords[1] : null,
          centroidLng: coords ? coords[0] : null,
        });
      } catch (err) {
        logger.error('Failed to cache representatives', { postalCode: code, error: err });
      }
    };

    // Don't await — fire and forget
    cacheWrite();

    // 4. Build response from API data
    return {
      source: 'api' as const,
      postalCode: code,
      location: {
        city: apiResponse.city ?? null,
        province: apiResponse.province ?? null,
      },
      representatives: uniqueReps.map((rep) => ({
        id: null,
        postalCode: code,
        name: rep.name || null,
        email: rep.email || null,
        districtName: rep.district_name || null,
        electedOffice: rep.elected_office || null,
        partyName: rep.party_name || null,
        representativeSetName: rep.representative_set_name || null,
        url: rep.url || null,
        photoUrl: rep.photo_url || null,
        offices: rep.offices ?? null,
        cachedAt: new Date().toISOString(),
      })),
    };
  },

  async findAll(filters: ListRepresentativesInput) {
    const { page, limit, search, postalCode } = filters;
    const skip = (page - 1) * limit;

    const where: Prisma.RepresentativeWhereInput = {};

    if (postalCode) {
      where.postalCode = postalCode;
    }

    if (search) {
      where.OR = [
        { name: { contains: search, mode: 'insensitive' } },
        { email: { contains: search, mode: 'insensitive' } },
        { districtName: { contains: search, mode: 'insensitive' } },
        { electedOffice: { contains: search, mode: 'insensitive' } },
      ];
    }

    const [representatives, total] = await Promise.all([
      prisma.representative.findMany({
        where,
        skip,
        take: limit,
        orderBy: { cachedAt: 'desc' },
      }),
      prisma.representative.count({ where }),
    ]);

    return {
      representatives,
      pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
    };
  },

  async findById(id: string) {
    const rep = await prisma.representative.findUnique({ where: { id } });
    if (!rep) {
      throw new AppError(404, 'Representative not found', 'REPRESENTATIVE_NOT_FOUND');
    }
    return rep;
  },

  async clearByPostalCode(code: string) {
    const result = await prisma.representative.deleteMany({
      where: { postalCode: code },
    });
    return { deleted: result.count, postalCode: code };
  },

  async deleteById(id: string) {
    const existing = await prisma.representative.findUnique({ where: { id } });
    if (!existing) {
      throw new AppError(404, 'Representative not found', 'REPRESENTATIVE_NOT_FOUND');
    }
    await prisma.representative.delete({ where: { id } });
  },

  async testApiConnection() {
    return representApiClient.testConnection();
  },

  async getCacheStats() {
    const [totalReps, postalCodesWithReps, totalPostalCodes] = await Promise.all([
      prisma.representative.count(),
      prisma.representative.groupBy({ by: ['postalCode'] }).then((g) => g.length),
      prisma.postalCodeCache.count(),
    ]);

    return {
      totalRepresentatives: totalReps,
      postalCodesWithRepresentatives: postalCodesWithReps,
      totalPostalCodes,
    };
  },
};
