import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Logger } from '@nestjs/common';
import { Job } from 'bullmq';
import { PrismaService } from '../../prisma/prisma.service';
import { ConfigService } from '@nestjs/config';
import { Gender } from '@prisma/client';

export interface SyncJobData {
  clubId: string;
  syncLogId: string;
  integrationId: string;
  direction: 'inbound' | 'outbound';
  entityType: string;
  apiUrl: string;
  apiKey: string;
}

interface OneCEntity {
  id: string;
  [key: string]: any;
}

@Processor('integration-sync')
export class IntegrationSyncProcessor extends WorkerHost {
  private readonly logger = new Logger(IntegrationSyncProcessor.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly config: ConfigService,
  ) {
    super();
  }

  async process(job: Job<SyncJobData>): Promise<void> {
    const { clubId, syncLogId, direction, entityType, apiUrl, apiKey } = job.data;

    this.logger.log(
      `Starting sync: direction=${direction}, entity=${entityType}, club=${clubId}`,
    );

    let recordsProcessed = 0;
    let recordsFailed = 0;
    let errorDetails: string | null = null;

    try {
      if (direction === 'inbound') {
        const result = await this.pullFromOnec(clubId, entityType, apiUrl, apiKey);
        recordsProcessed = result.processed;
        recordsFailed = result.failed;
        errorDetails = result.errors.length > 0 ? result.errors.join('; ') : null;
      } else {
        const result = await this.pushToOnec(clubId, entityType, apiUrl, apiKey);
        recordsProcessed = result.processed;
        recordsFailed = result.failed;
        errorDetails = result.errors.length > 0 ? result.errors.join('; ') : null;
      }

      await this.prisma.syncLog.update({
        where: { id: syncLogId },
        data: {
          recordsProcessed,
          recordsFailed,
          errorDetails,
          completedAt: new Date(),
        },
      });

      await this.prisma.integrationConfig.update({
        where: { clubId },
        data: { lastSyncAt: new Date() },
      });

      this.logger.log(
        `Sync completed: ${entityType} — ${recordsProcessed} processed, ${recordsFailed} failed`,
      );
    } catch (error: any) {
      this.logger.error(`Sync failed for ${entityType}: ${error.message}`);

      await this.prisma.syncLog.update({
        where: { id: syncLogId },
        data: {
          recordsProcessed,
          recordsFailed,
          errorDetails: error.message?.slice(0, 2000),
          completedAt: new Date(),
        },
      });

      throw error; // BullMQ will retry
    }
  }

  // ─── Pull from 1C → MyFitCRM ──────────────────────────────────────

  private async pullFromOnec(
    clubId: string,
    entityType: string,
    apiUrl: string,
    apiKey: string,
  ): Promise<{ processed: number; failed: number; errors: string[] }> {
    const errors: string[] = [];
    let processed = 0;
    let failed = 0;

    const data = await this.fetchFromOnec(apiUrl, apiKey, entityType);

    for (const item of data) {
      try {
        switch (entityType) {
          case 'clients':
            await this.upsertClientFromOnec(clubId, item);
            break;
          case 'services':
            await this.upsertServiceFromOnec(clubId, item);
            break;
          case 'sales':
            await this.upsertSaleFromOnec(clubId, item);
            break;
          case 'work_schedule':
            await this.upsertWorkScheduleFromOnec(clubId, item);
            break;
          default:
            throw new Error(`Unknown entity type: ${entityType}`);
        }
        processed++;
      } catch (err: any) {
        failed++;
        errors.push(`${entityType}[${item.id ?? '?'}]: ${err.message}`);
        if (errors.length >= 50) {
          errors.push(`... and more (stopped collecting after 50 errors)`);
          break;
        }
      }
    }

    return { processed, failed, errors };
  }

  // ─── Push from MyFitCRM → 1C ──────────────────────────────────────

  private async pushToOnec(
    clubId: string,
    entityType: string,
    apiUrl: string,
    apiKey: string,
  ): Promise<{ processed: number; failed: number; errors: string[] }> {
    const errors: string[] = [];
    let processed = 0;
    let failed = 0;

    let items: any[] = [];

    switch (entityType) {
      case 'clients':
        items = await this.prisma.client.findMany({
          where: { clubId },
          take: 1000,
          orderBy: { updatedAt: 'desc' },
        });
        break;
      case 'sales':
        items = await this.prisma.sale.findMany({
          where: { clubId },
          take: 1000,
          orderBy: { updatedAt: 'desc' },
        });
        break;
      case 'work_schedule':
        items = await this.prisma.workSchedule.findMany({
          where: { clubId },
          take: 1000,
          orderBy: { date: 'desc' },
        });
        break;
      default:
        throw new Error(`Unknown entity type for outbound sync: ${entityType}`);
    }

    for (const item of items) {
      try {
        await this.sendToOnec(apiUrl, apiKey, entityType, this.mapToOnecFormat(entityType, item));
        processed++;
      } catch (err: any) {
        failed++;
        errors.push(`${entityType}[${item.id}]: ${err.message}`);
        if (errors.length >= 50) break;
      }
    }

    return { processed, failed, errors };
  }

  // ─── 1C API Communication ───────────────────────────────────────

  private async fetchFromOnec(
    apiUrl: string,
    apiKey: string,
    entityType: string,
  ): Promise<OneCEntity[]> {
    const endpoint = `${apiUrl.replace(/\/$/, '')}/${entityType}`;

    try {
      const response = await fetch(endpoint, {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        signal: AbortSignal.timeout(30000),
      });

      if (!response.ok) {
        throw new Error(`1C API returned ${response.status}: ${await response.text()}`);
      }

      const body = await response.json() as any;
      // 1C may return {value: [...]} (OData) or plain array
      return Array.isArray(body) ? body : (body.value ?? body.data ?? []);
    } catch (err: any) {
      if (err.name === 'AbortError') {
        throw new Error(`1C API timeout (30s) for ${entityType}`);
      }
      throw err;
    }
  }

  private async sendToOnec(
    apiUrl: string,
    apiKey: string,
    entityType: string,
    data: Record<string, any>,
  ): Promise<void> {
    const endpoint = `${apiUrl.replace(/\/$/, '')}/${entityType}`;

    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
      signal: AbortSignal.timeout(15000),
    });

    if (!response.ok) {
      throw new Error(`1C API POST ${response.status}`);
    }
  }

  // ─── Data Mapping: 1C → MyFitCRM ─────────────────────────────────

  private async upsertClientFromOnec(clubId: string, data: OneCEntity): Promise<void> {
    const externalId = String(data.id ?? data.Code ?? data.Ref_Key);
    if (!externalId) throw new Error('Missing external ID');

    const mapped = {
      firstName: data.firstName ?? data.Name?.split(' ')[0] ?? data.Наименование?.split(' ')[0] ?? '',
      lastName: data.lastName ?? data.Name?.split(' ').slice(1).join(' ') ?? data.Наименование?.split(' ').slice(1).join(' ') ?? '',
      phone: data.phone ?? data.Phone ?? data.Телефон ?? null,
      gender: this.mapGender(data.gender ?? data.Пол),
      source: '1c_sync',
    };

    // Upsert by externalId (stored in client metadata)
    const existing = await this.prisma.client.findFirst({
      where: { clubId, externalId },
    });

    if (existing) {
      await this.prisma.client.update({
        where: { id: existing.id },
        data: { ...mapped, updatedAt: new Date() },
      });
    } else {
      await this.prisma.client.create({
        data: { clubId, externalId, ...mapped },
      });
    }
  }

  private async upsertServiceFromOnec(clubId: string, data: OneCEntity): Promise<void> {
    const name = data.name ?? data.Name ?? data.Наименование;
    if (!name) throw new Error('Service name is required');

    const existing = await this.prisma.service.findFirst({
      where: { clubId, name },
    });

    const mapped = {
      name,
      description: data.description ?? data.Description ?? data.Описание ?? null,
      price: Number(data.price ?? data.Price ?? data.Цена ?? 0),
      duration: Number(data.duration ?? data.Duration ?? 60),
    };

    if (existing) {
      await this.prisma.service.update({
        where: { id: existing.id },
        data: mapped,
      });
    } else {
      await this.prisma.service.create({
        data: { clubId, ...mapped },
      });
    }
  }

  private async upsertSaleFromOnec(clubId: string, data: OneCEntity): Promise<void> {
    const externalId = String(data.id ?? data.Ref_Key);
    if (!externalId) throw new Error('Missing sale external ID');

    // Find the client by phone or external ID
    const clientPhone = data.clientPhone ?? data.Телефон;
    let client = clientPhone
      ? await this.prisma.client.findFirst({ where: { clubId, phone: clientPhone } })
      : null;

    if (!client) {
      this.logger.warn(`Client not found for sale ${externalId}, skipping`);
      throw new Error(`Client not found for phone ${clientPhone}`);
    }

    const amount = Number(data.amount ?? data.Amount ?? data.Сумма ?? 0);
    const status = data.status === 'paid' || data.Оплачен === true ? 'PAID' : 'PENDING';

    const existingSale = await this.prisma.sale.findFirst({
      where: { clubId, externalId },
    });

    if (existingSale) {
      await this.prisma.sale.update({
        where: { id: existingSale.id },
        data: { amount, status, updatedAt: new Date() },
      });
    } else {
      await this.prisma.sale.create({
        data: {
          clubId,
          externalId,
          clientId: client.id,
          trainerId: client.assignedTrainerId ?? (await this.getDefaultTrainer(clubId)),
          amount,
          status,
          description: data.description ?? data.Комментарий ?? '1C sync',
        },
      });
    }
  }

  private async upsertWorkScheduleFromOnec(clubId: string, data: OneCEntity): Promise<void> {
    const trainerPhone = data.trainerPhone ?? data.ТелефонТренера;
    if (!trainerPhone) throw new Error('Trainer phone required for schedule sync');

    const trainer = await this.prisma.user.findFirst({
      where: { clubId, phone: trainerPhone, role: 'TRAINER' },
    });
    if (!trainer) throw new Error(`Trainer not found: ${trainerPhone}`);

    const date = new Date(data.date ?? data.Дата);
    const startTime = data.startTime ?? data.НачалоРаботы ?? '09:00';
    const endTime = data.endTime ?? data.КонецРаботы ?? '18:00';

    const existing = await this.prisma.workSchedule.findFirst({
      where: { clubId, trainerId: trainer.id, date },
    });

    if (existing) {
      await this.prisma.workSchedule.update({
        where: { id: existing.id },
        data: { startTime, endTime, isAvailable: true },
      });
    } else {
      await this.prisma.workSchedule.create({
        data: {
          clubId,
          trainerId: trainer.id,
          date,
          startTime,
          endTime,
          isAvailable: true,
        },
      });
    }
  }

  // ─── Data Mapping: MyFitCRM → 1C ─────────────────────────────────

  private mapToOnecFormat(entityType: string, item: any): Record<string, any> {
    switch (entityType) {
      case 'clients':
        return {
          ExternalId: item.id,
          Name: `${item.firstName} ${item.lastName}`.trim(),
          Phone: item.phone,
          Gender: item.gender,
          CreatedAt: item.createdAt,
        };
      case 'sales':
        return {
          ExternalId: item.id,
          ClientId: item.clientId,
          Amount: Number(item.amount),
          Status: item.status,
          Description: item.description,
          CreatedAt: item.createdAt,
        };
      case 'work_schedule':
        return {
          TrainerId: item.trainerId,
          Date: item.date,
          StartTime: item.startTime,
          EndTime: item.endTime,
          IsAvailable: item.isAvailable,
        };
      default:
        return item;
    }
  }

  // ─── Helpers ────────────────────────────────────────────────────

  private mapGender(value: any): Gender | null {
    if (!value) return null;
    const v = String(value).toLowerCase();
    if (v === 'male' || v === 'м' || v === 'мужской') return Gender.MALE;
    if (v === 'female' || v === 'ж' || v === 'женский') return Gender.FEMALE;
    return null;
  }

  private async getDefaultTrainer(clubId: string): Promise<string> {
    const trainer = await this.prisma.user.findFirst({
      where: { clubId, role: 'TRAINER' },
      select: { id: true },
    });
    if (!trainer) throw new Error('No trainers found in club');
    return trainer.id;
  }
}
