import { env } from '../config/env';
import { prisma } from '../config/database';
import { logger } from '../utils/logger';
import { listmonkClient, BulkSyncResult } from './listmonk.client';

// --- List definitions ---

const LIST_DEFINITIONS: Array<{ name: string; tags: string[] }> = [
  { name: 'All Contacts', tags: ['v2'] },
  { name: 'Campaign Participants', tags: ['v2', 'influence'] },
  { name: 'Locations - All', tags: ['v2', 'map'] },
  { name: 'Support Level 1 (Strong)', tags: ['v2', 'map', 'support'] },
  { name: 'Support Level 2 (Likely)', tags: ['v2', 'map', 'support'] },
  { name: 'Support Level 3 (Unsure)', tags: ['v2', 'map', 'support'] },
  { name: 'Support Level 4 (Opposition)', tags: ['v2', 'map', 'support'] },
  { name: 'Has Campaign Sign', tags: ['v2', 'map', 'signs'] },
  { name: 'Users', tags: ['v2', 'users'] },
  { name: 'Volunteers', tags: ['v2', 'map', 'shifts'] },
  { name: 'Canvassers', tags: ['v2', 'map', 'canvass'] },
  { name: 'Subscribers', tags: ['v2', 'payments'] },
  { name: 'Donors', tags: ['v2', 'payments'] },
];

export const SUPPORT_LEVEL_LIST_MAP: Record<string, string> = {
  LEVEL_1: 'Support Level 1 (Strong)',
  LEVEL_2: 'Support Level 2 (Likely)',
  LEVEL_3: 'Support Level 3 (Unsure)',
  LEVEL_4: 'Support Level 4 (Opposition)',
};

class ListmonkSyncService {
  private listIds: Record<string, number> = {};
  private initialized = false;
  private lastSyncAt: Date | null = null;
  private lastError: string | null = null;

  async initializeLists(): Promise<void> {
    const existingLists = await listmonkClient.getLists();
    const existingByName = new Map(existingLists.map(l => [l.name, l]));

    for (const def of LIST_DEFINITIONS) {
      const existing = existingByName.get(def.name);
      if (existing) {
        this.listIds[def.name] = existing.id;
      } else {
        const created = await listmonkClient.createList(def.name, 'private', def.tags);
        this.listIds[def.name] = created.id;
        logger.info(`Created Listmonk list: ${def.name} (id=${created.id})`);
      }
    }

    this.initialized = true;
    logger.info('Listmonk lists initialized', { listIds: this.listIds });
  }

  async ensureInitialized(): Promise<void> {
    if (!this.initialized) {
      await this.initializeLists();
    }
  }

  getListId(name: string): number | undefined {
    return this.listIds[name];
  }

  async syncCampaignParticipants(): Promise<BulkSyncResult> {
    await this.ensureInitialized();
    const result: BulkSyncResult = { total: 0, success: 0, failed: 0, errors: [] };

    // Get distinct senders from campaign emails
    const emails = await prisma.campaignEmail.findMany({
      where: { userEmail: { not: null } },
      distinct: ['userEmail'],
      select: {
        userEmail: true,
        userName: true,
        userPostalCode: true,
        campaignSlug: true,
        recipientName: true,
        sentAt: true,
      },
      orderBy: { sentAt: 'desc' },
    });

    result.total = emails.length;
    const allContactsId = this.listIds['All Contacts'];
    const participantsId = this.listIds['Campaign Participants'];

    for (const email of emails) {
      if (!email.userEmail) continue;
      try {
        await listmonkClient.upsertSubscriber(
          email.userEmail,
          email.userName || '',
          [allContactsId, participantsId],
          {
            source: 'campaign_participant',
            campaign_slug: email.campaignSlug,
            postal_code: email.userPostalCode || null,
            last_sent: email.sentAt.toISOString(),
            recipient_name: email.recipientName || null,
          },
        );
        result.success++;
      } catch (err) {
        result.failed++;
        const msg = `Failed to sync participant ${email.userEmail}: ${err instanceof Error ? err.message : String(err)}`;
        result.errors.push(msg);
        logger.warn(msg);
      }
    }

    return result;
  }

  async syncLocations(): Promise<BulkSyncResult> {
    await this.ensureInitialized();
    const result: BulkSyncResult = { total: 0, success: 0, failed: 0, errors: [] };

    // Query addresses (unit-level) with email, include location for street address
    const addresses = await prisma.address.findMany({
      where: { email: { not: null } },
      select: {
        email: true,
        firstName: true,
        lastName: true,
        supportLevel: true,
        sign: true,
        signSize: true,
        phone: true,
        location: {
          select: {
            address: true,
          },
        },
      },
    });

    result.total = addresses.length;
    const allContactsId = this.listIds['All Contacts'];
    const locationsAllId = this.listIds['Locations - All'];

    for (const addr of addresses) {
      if (!addr.email) continue;
      try {
        const listIds = [allContactsId, locationsAllId];

        // Add support level list
        if (addr.supportLevel && SUPPORT_LEVEL_LIST_MAP[addr.supportLevel]) {
          const levelListId = this.listIds[SUPPORT_LEVEL_LIST_MAP[addr.supportLevel]];
          if (levelListId) listIds.push(levelListId);
        }

        // Add sign list
        if (addr.sign) {
          const signListId = this.listIds['Has Campaign Sign'];
          if (signListId) listIds.push(signListId);
        }

        const name = [addr.firstName, addr.lastName].filter(Boolean).join(' ');
        await listmonkClient.upsertSubscriber(
          addr.email,
          name,
          listIds,
          {
            source: 'location',
            address: addr.location.address || null,
            support_level: addr.supportLevel || null,
            sign: addr.sign,
            sign_size: addr.signSize || null,
            phone: addr.phone || null,
          },
        );
        result.success++;
      } catch (err) {
        result.failed++;
        const msg = `Failed to sync address ${addr.email}: ${err instanceof Error ? err.message : String(err)}`;
        result.errors.push(msg);
        logger.warn(msg);
      }
    }

    return result;
  }

  async syncUsers(): Promise<BulkSyncResult> {
    await this.ensureInitialized();
    const result: BulkSyncResult = { total: 0, success: 0, failed: 0, errors: [] };

    const users = await prisma.user.findMany({
      where: {
        status: 'ACTIVE',
        role: { not: 'TEMP' },
      },
      select: {
        email: true,
        name: true,
        role: true,
        createdAt: true,
      },
    });

    result.total = users.length;
    const allContactsId = this.listIds['All Contacts'];
    const usersListId = this.listIds['Users'];

    for (const user of users) {
      try {
        await listmonkClient.upsertSubscriber(
          user.email,
          user.name || '',
          [allContactsId, usersListId],
          {
            source: 'user',
            role: user.role,
            created_at: user.createdAt.toISOString(),
          },
        );
        result.success++;
      } catch (err) {
        result.failed++;
        const msg = `Failed to sync user ${user.email}: ${err instanceof Error ? err.message : String(err)}`;
        result.errors.push(msg);
        logger.warn(msg);
      }
    }

    return result;
  }

  async syncCrmTags(): Promise<BulkSyncResult> {
    await this.ensureInitialized();
    const result: BulkSyncResult = { total: 0, success: 0, failed: 0, errors: [] };

    // Find all CRM tags that have a Listmonk list linked
    const crmTags = await prisma.crmTag.findMany({
      where: { listmonkListId: { not: null } },
    });

    for (const tag of crmTags) {
      try {
        // Find all contacts with this tag using raw SQL (JSONB query)
        const contacts = await prisma.$queryRaw<{ email: string; displayName: string }[]>`
          SELECT email, "displayName"
          FROM contacts
          WHERE "mergedIntoId" IS NULL
            AND email IS NOT NULL
            AND tags @> ${JSON.stringify([tag.name])}::jsonb
        `;

        result.total += contacts.length;

        for (const contact of contacts) {
          try {
            await listmonkClient.upsertSubscriber(
              contact.email,
              contact.displayName || '',
              [tag.listmonkListId!],
              { source: 'crm_tag', tag_name: tag.name, last_synced: new Date().toISOString() },
            );
            result.success++;
          } catch (err) {
            result.failed++;
            const msg = `Failed to sync tag "${tag.name}" for ${contact.email}: ${err instanceof Error ? err.message : String(err)}`;
            result.errors.push(msg);
            logger.warn(msg);
          }
        }

        // Update denormalized count
        const countResult = await prisma.$queryRaw<{ count: bigint }[]>`
          SELECT COUNT(*) as count
          FROM contacts
          WHERE "mergedIntoId" IS NULL
            AND tags @> ${JSON.stringify([tag.name])}::jsonb
        `;
        await prisma.crmTag.update({
          where: { id: tag.id },
          data: { contactCount: Number(countResult[0]?.count ?? 0) },
        });
      } catch (err) {
        const msg = `Failed to sync CRM tag "${tag.name}": ${err instanceof Error ? err.message : String(err)}`;
        result.errors.push(msg);
        logger.warn(msg);
      }
    }

    return result;
  }

  async syncAll(): Promise<{ participants: BulkSyncResult; locations: BulkSyncResult; users: BulkSyncResult; crmTags: BulkSyncResult }> {
    this.lastError = null;
    try {
      const participants = await this.syncCampaignParticipants();
      const locations = await this.syncLocations();
      const users = await this.syncUsers();
      const crmTags = await this.syncCrmTags();
      this.lastSyncAt = new Date();
      return { participants, locations, users, crmTags };
    } catch (err) {
      this.lastError = err instanceof Error ? err.message : String(err);
      throw err;
    }
  }

  getStatus(): {
    enabled: boolean;
    connected: boolean;
    initialized: boolean;
    lastSyncAt: string | null;
    lastError: string | null;
  } {
    return {
      enabled: env.LISTMONK_SYNC_ENABLED === 'true',
      connected: false, // Caller should use testConnection for live check
      initialized: this.initialized,
      lastSyncAt: this.lastSyncAt?.toISOString() || null,
      lastError: this.lastError,
    };
  }

  async getStats(): Promise<{ lists: Array<{ name: string; subscriberCount: number }> }> {
    await this.ensureInitialized();
    const lists = await listmonkClient.getLists();
    // Only return lists matching our definitions
    const ourListNames = new Set(LIST_DEFINITIONS.map(d => d.name));
    return {
      lists: lists
        .filter(l => ourListNames.has(l.name))
        .map(l => ({ name: l.name, subscriberCount: l.subscriber_count })),
    };
  }

  async reinitialize(): Promise<void> {
    this.initialized = false;
    this.listIds = {};
    await this.initializeLists();
  }

  setLastSyncAt(date: Date): void {
    this.lastSyncAt = date;
  }

  setLastError(error: string | null): void {
    this.lastError = error;
  }
}

export const listmonkSyncService = new ListmonkSyncService();
