import { prisma, ExposureSource, ExposureSeverity, WatchlistType } from '@shieldai/db';
import { createHash } from 'crypto';

function hashIdentifier(identifier: string): string {
  return createHash('sha256').update(identifier.toLowerCase().trim()).digest('hex');
}

function determineSeverity(
  source: ExposureSource,
  dataType: WatchlistType
): ExposureSeverity {
  const criticalSources = [ExposureSource.darkWebForum, ExposureSource.honeypot];
  const warningSources = [ExposureSource.hibp, ExposureSource.shodan];
  const criticalTypes = [WatchlistType.ssn];

  if (criticalTypes.includes(dataType)) return ExposureSeverity.critical;
  if (criticalSources.includes(source)) return ExposureSeverity.critical;
  if (warningSources.includes(source)) return ExposureSeverity.warning;
  return ExposureSeverity.info;
}

export class ScanService {
  async checkHIBP(email: string): Promise<{ exposed: boolean; sources: string[] }> {
    try {
      const response = await fetch(
        `https://hibp.com/api/v2/${encodeURIComponent(email)}`,
        {
          headers: {
            'hibp-api-key': process.env.HIBP_API_KEY || '',
            Accept: 'application/json',
          },
          signal: AbortSignal.timeout(15000),
        }
      );

      if (response.status === 404) {
        return { exposed: false, sources: [] };
      }

      if (!response.ok) {
        console.error(`[ScanService:HIBP] Status ${response.status} for ${email}`);
        return { exposed: false, sources: [] };
      }

      const data = await response.json();
      const sources = Array.isArray(data)
        ? data.map((p: { Name: string }) => p.Name)
        : [];

      return { exposed: sources.length > 0, sources };
    } catch (error) {
      console.error('[ScanService:HIBP] Error:', error);
      return { exposed: false, sources: [] };
    }
  }

  async checkShodan(domain: string): Promise<{ exposed: boolean; ports: string[]; ips: string[] }> {
    try {
      const response = await fetch(
        `https://api.shodan.io/shodan/host/${encodeURIComponent(domain)}`,
        {
          headers: {
            Authorization: `Bearer ${process.env.SHODAN_API_KEY || ''}`,
          },
          signal: AbortSignal.timeout(15000),
        }
      );

      if (response.status === 404) {
        return { exposed: false, ports: [], ips: [] };
      }

      if (!response.ok) {
        console.error(`[ScanService:Shodan] Status ${response.status} for ${domain}`);
        return { exposed: false, ports: [], ips: [] };
      }

      const data = await response.json();
      return {
        exposed: !!data.ip_str,
        ports: data.ports?.map(String) || [],
        ips: [data.ip_str || ''],
      };
    } catch (error) {
      console.error('[ScanService:Shodan] Error:', error);
      return { exposed: false, ports: [], ips: [] };
    }
  }

  async processSubscriptionScan(
    subscriptionId: string,
    watchlistItems: Awaited<ReturnType<ScanService['getWatchlistItems']>>
  ): Promise<{ exposuresCreated: number; exposuresUpdated: number }> {
    let exposuresCreated = 0;
    let exposuresUpdated = 0;

    for (const item of watchlistItems) {
      const identifier = item.value;
      const identifierHash = hashIdentifier(identifier);

      switch (item.type) {
        case WatchlistType.email: {
          const hibpResult = await this.checkHIBP(identifier);
          if (hibpResult.exposed) {
            for (const source of hibpResult.sources) {
              const existing = await prisma.exposure.findFirst({
                where: {
                  subscriptionId,
                  source: ExposureSource.hibp,
                  identifierHash,
                  metadata: { path: ['dbName'], equals: source },
                },
              });

              if (existing) {
                await prisma.exposure.update({
                  where: { id: existing.id },
                  data: { detectedAt: new Date() },
                });
                exposuresUpdated++;
              } else {
                await prisma.exposure.create({
                  data: {
                    subscriptionId,
                    watchlistItemId: item.id,
                    source: ExposureSource.hibp,
                    dataType: item.type,
                    identifier,
                    identifierHash,
                    severity: determineSeverity(ExposureSource.hibp, item.type),
                    isFirstTime: true,
                    metadata: { dbName: source },
                    detectedAt: new Date(),
                  },
                });
                exposuresCreated++;
              }
            }
          }
          break;
        }

        case WatchlistType.domain: {
          const shodanResult = await this.checkShodan(identifier);
          if (shodanResult.exposed) {
            const existing = await prisma.exposure.findFirst({
              where: {
                subscriptionId,
                source: ExposureSource.shodan,
                identifierHash,
              },
            });

            if (existing) {
              await prisma.exposure.update({
                where: { id: existing.id },
                data: {
                  detectedAt: new Date(),
                  metadata: { ports: shodanResult.ports, ips: shodanResult.ips },
                },
              });
              exposuresUpdated++;
            } else {
              await prisma.exposure.create({
                data: {
                  subscriptionId,
                  watchlistItemId: item.id,
                  source: ExposureSource.shodan,
                  dataType: item.type,
                  identifier,
                  identifierHash,
                  severity: determineSeverity(ExposureSource.shodan, item.type),
                  isFirstTime: true,
                  metadata: { ports: shodanResult.ports, ips: shodanResult.ips },
                  detectedAt: new Date(),
                },
              });
              exposuresCreated++;
            }
          }
          break;
        }

        default: {
          const existing = await prisma.exposure.findFirst({
            where: { subscriptionId, watchlistItemId: item.id, identifierHash },
          });

          if (!existing) {
            await prisma.exposure.create({
              data: {
                subscriptionId,
                watchlistItemId: item.id,
                source: ExposureSource.darkWebForum,
                dataType: item.type,
                identifier,
                identifierHash,
                severity: determineSeverity(ExposureSource.darkWebForum, item.type),
                isFirstTime: true,
                detectedAt: new Date(),
              },
            });
            exposuresCreated++;
          }
          break;
        }
      }
    }

    return { exposuresCreated, exposuresUpdated };
  }

  async getWatchlistItems(subscriptionId: string) {
    return prisma.watchlistItem.findMany({
      where: { subscriptionId, isActive: true },
    });
  }
}

export const scanService = new ScanService();
