import { PrismaClient, SpamFeedback, SpamRule, SpamAuditLog } from '@prisma/client';
import { FieldEncryptionService } from '@shieldai/db';
import { generateRequestId } from '@shieldai/types';
import { emitSpamShieldAlert } from '@shieldai/correlation';
import { spamConfig, spamFeatureFlags, metadataLimits } from '../config/spamshield.config';
import { CircuitBreaker, CircuitBreakerError, CircuitState, CircuitBreakerMetrics } from '../circuit-breaker';
import { validatePhoneNumber as validateE164 } from '../utils/phone-validation';
import { CarrierApi, CarrierCall, CarrierSms, CarrierDecision } from '../carriers/carrier-types';
import { CarrierFactory, CarrierType } from '../carriers/carrier-factory';
import { DecisionEngine, DecisionContext, DecisionResult } from '../engine/decision-engine';
import { RuleEngine, RuleMatch } from '../engine/rule-engine';
import { AlertServer, AlertEvent } from '../websocket/alert-server';
import { BertSmsClassifier, SmsClassificationResult } from '../classifier/sms-classifier';

const prisma = new PrismaClient() as PrismaClient & {
  spamFeedback: {
    create: (data: { data: SpamFeedback }) => Promise<SpamFeedback>;
  };
  spamRule: {
    findMany: (args: { where: { isActive: boolean } }) => Promise<SpamRule[]>;
  };
  spamAuditLog: {
    create: (data: { data: SpamAuditLog }) => Promise<SpamAuditLog>;
  };
};

interface InitializationLock {
  promise: Promise<void>;
  resolved: boolean;
}

export interface ReputationResult {
  score: number;
  isSpam: boolean;
  source: 'hiya' | 'truecaller' | 'combined' | 'fallback';
  hiyaScore?: number;
  truecallerScore?: number;
}

export interface CircuitMetrics {
  hiya: CircuitBreakerMetrics;
  truecaller: CircuitBreakerMetrics;
}

export interface IncomingCall {
  callId: string;
  phoneNumber: string;
  from: string;
  to: string;
  startTime: Date;
  direction: 'inbound' | 'outbound';
  carrierType: CarrierType;
  carrierSid: string;
  requestId?: string;
}

export interface IncomingSms {
  messageId: string;
  phoneNumber: string;
  from: string;
  to: string;
  body: string;
  timestamp: Date;
  direction: 'inbound' | 'outbound';
  carrierType: CarrierType;
  carrierSid: string;
  requestId?: string;
}

export class SpamShieldService {
  private static instance: SpamShieldService;
  private initLock: InitializationLock | null = null;
  private hiyaBreaker: CircuitBreaker = new CircuitBreaker({
    failureThreshold: spamConfig.circuitBreakerThreshold,
    timeout: spamConfig.circuitBreakerTimeout,
  });
  private truecallerBreaker: CircuitBreaker = new CircuitBreaker({
    failureThreshold: spamConfig.circuitBreakerThreshold,
    timeout: spamConfig.circuitBreakerTimeout,
  });
  
  // Carrier integration
  private carrierFactory?: CarrierFactory;
  
  // Decision engine
  private decisionEngine?: DecisionEngine;
  private ruleEngine?: RuleEngine;
  
  // WebSocket alert server
  private alertServer?: AlertServer;
  
  // SMS Classifier
  private smsClassifier?: BertSmsClassifier;

  private constructor() {}

  static getInstance(): SpamShieldService {
    if (!SpamShieldService.instance) {
      SpamShieldService.instance = new SpamShieldService();
    }
    return SpamShieldService.instance;
  }

  async initialize(): Promise<void> {
    if (this.initLock?.resolved) return;

    if (!this.initLock) {
      this.initLock = {
        promise: this._initialize(),
        resolved: false,
      };
    }

    await this.initLock.promise;
  }

  private async _initialize(): Promise<void> {
    this.hiyaBreaker = new CircuitBreaker({
      failureThreshold: spamConfig.circuitBreakerThreshold,
      timeout: spamConfig.circuitBreakerTimeout,
      onStateChange: (state: CircuitState, previous: CircuitState) => {
        console.log(`[SpamShield] [req:${generateRequestId()}] Hiya circuit: ${previous} -> ${state}`);
      },
    });
    this.truecallerBreaker = new CircuitBreaker({
      failureThreshold: spamConfig.circuitBreakerThreshold,
      timeout: spamConfig.circuitBreakerTimeout,
      onStateChange: (state: CircuitState, previous: CircuitState) => {
        console.log(`[SpamShield] [req:${generateRequestId()}] Truecaller circuit: ${previous} -> ${state}`);
      },
    });
    
    // Initialize SMS Classifier with feature flag check
    if (spamFeatureFlags.enableSMSClassification) {
      this.smsClassifier = new BertSmsClassifier(this);
      console.log(`[SpamShield] [req:${generateRequestId()}] SMS Classifier initialized`);
    } else {
      console.log(`[SpamShield] [req:${generateRequestId()}] SMS Classification disabled via feature flag`);
    }
    
    this.initLock!.resolved = true;
  }

  async checkReputation(phoneNumber: string): Promise<ReputationResult> {
    const validated = this.validatePhoneNumber(phoneNumber);

    const results = await Promise.allSettled([
      this.fetchHiyaReputation(validated),
      this.fetchTruecallerReputation(validated),
    ]);

    const hiyaResult = results[0];
    const truecallerResult = results[1];

    const hiyaScore = hiyaResult.status === 'fulfilled' ? hiyaResult.value : undefined;
    const truecallerScore = truecallerResult.status === 'fulfilled' ? truecallerResult.value : undefined;

    if (hiyaScore !== undefined && truecallerScore !== undefined) {
      const combinedScore = (hiyaScore + truecallerScore) / 2;
      const isSpam = combinedScore > spamConfig.defaultConfidenceThreshold;
      return {
        score: combinedScore,
        isSpam,
        source: 'combined',
        hiyaScore,
        truecallerScore,
      };
    }

    if (hiyaScore !== undefined) {
      return {
        score: hiyaScore,
        isSpam: hiyaScore > spamConfig.defaultConfidenceThreshold,
        source: 'hiya',
        hiyaScore,
      };
    }

    if (truecallerScore !== undefined) {
      return {
        score: truecallerScore,
        isSpam: truecallerScore > spamConfig.defaultConfidenceThreshold,
        source: 'truecaller',
        truecallerScore,
      };
    }

    return {
      score: 0,
      isSpam: false,
      source: 'fallback',
    };
  }

  async analyzeCall(phoneNumber: string, callTimestamp: Date): Promise<{
    decision: 'BLOCK' | 'FLAG' | 'ALLOW';
    confidence: number;
    ruleMatches: string[];
  }> {
    if (!spamFeatureFlags.enableCallAnalysis) {
      throw new Error('Call analysis disabled via feature flag');
    }

    const validated = this.validatePhoneNumber(phoneNumber);
    const matches = this.ruleEngine
      ? await this.ruleEngine.evaluate(validated)
      : [];

    const ruleMatchIds = matches.map(m => m.ruleId);
    const confidence = Math.min(matches.reduce((sum, m) => sum + m.score, 0), 1.0);
    const decision = confidence > 0.8 ? 'BLOCK' : confidence > 0.5 ? 'FLAG' : 'ALLOW';

    const encrypted = FieldEncryptionService.encrypt(validated);

    const auditLog = await prisma.spamAuditLog.create({
      data: {
        userId: 'system',
        phoneNumber: encrypted,
        decision: decision as any,
        reason: `Rule-based analysis`,
        ruleId: ruleMatchIds[0],
      },
    });

    if (decision === 'BLOCK' || decision === 'FLAG') {
      emitSpamShieldAlert(
        'system',
        auditLog.id,
        validated,
        decision,
        confidence,
        ruleMatchIds
      ).catch((err) => console.error(`[Correlation] SpamShield emit failed:`, err));
    }

    return { decision, confidence, ruleMatches: ruleMatchIds };
  }

  async recordFeedback(
    userId: string,
    phoneNumber: string,
    isSpam: boolean,
    label?: string,
    metadata?: Record<string, any>
  ): Promise<void> {
    if (!spamFeatureFlags.enableFeedbackLoop) {
      throw new Error('Feedback loop disabled via feature flag');
    }

    if (!userId || typeof userId !== 'string' || userId.trim().length === 0) {
      throw new Error('Feedback: userId is required');
    }

    if (!phoneNumber || typeof phoneNumber !== 'string') {
      throw new Error('Feedback: phoneNumber must be a non-empty string');
    }

    if (typeof isSpam !== 'boolean') {
      throw new Error('Feedback: isSpam must be a boolean');
    }

    const validated = this.validatePhoneNumber(phoneNumber);
    const encrypted = FieldEncryptionService.encrypt(validated);
    const hash = FieldEncryptionService.hashPhoneNumber(validated);

    const validatedMetadata = metadata 
      ? this.validateMetadata(metadata)
      : { source: 'user_feedback' };

    await prisma.spamFeedback.create({
      data: {
        userId,
        phoneNumber: encrypted,
        phoneNumberHash: hash,
        isSpam,
        label,
        metadata: JSON.stringify(validatedMetadata),
      },
    });
  }

  getCircuitMetrics(): CircuitMetrics {
    return {
      hiya: this.hiyaBreaker.getMetrics(),
      truecaller: this.truecallerBreaker.getMetrics(),
    };
  }

  resetCircuits(): void {
    this.hiyaBreaker.reset();
    this.truecallerBreaker.reset();
  }

  // Carrier integration methods
  initializeCarrierFactory(config: Parameters<CarrierFactory['constructor']>[0]): void {
    this.carrierFactory = new CarrierFactory(config);
  }

  getCarrierFactory(): CarrierFactory | undefined {
    return this.carrierFactory;
  }

  async executeCarrierAction(
    carrierType: CarrierType,
    action: 'block' | 'flag' | 'allow',
    phoneNumber: string,
    sid: string,
    isSms: boolean = false
  ): Promise<void> {
    if (!this.carrierFactory) {
      throw new Error('Carrier factory not initialized');
    }

    const carrier = this.carrierFactory.createCarrier(carrierType);
    
    if (isSms) {
      switch (action) {
        case 'block':
          await carrier.blockSms(sid);
          break;
        case 'flag':
          await carrier.flagSms(sid);
          break;
        case 'allow':
          await carrier.allowSms(sid);
          break;
      }
    } else {
      switch (action) {
        case 'block':
          await carrier.blockCall(sid);
          break;
        case 'flag':
          await carrier.flagCall(sid);
          break;
        case 'allow':
          await carrier.allowCall(sid);
          break;
      }
    }

    await this.logCarrierAction(phoneNumber, action, carrierType, sid, isSms);
  }

  // Decision engine integration
  initializeDecisionEngine(
    ruleEngine: RuleEngine,
    config?: Parameters<DecisionEngine['constructor']>[2]
  ): void {
    this.ruleEngine = ruleEngine;
    this.decisionEngine = new DecisionEngine(this, ruleEngine, config);
  }

  getDecisionEngine(): DecisionEngine | undefined {
    return this.decisionEngine;
  }

  async makeRealTimeDecision(
    phoneNumber: string,
    context: Omit<DecisionContext, 'phoneNumber'>
  ): Promise<DecisionResult> {
    if (!this.decisionEngine) {
      throw new Error('Decision engine not initialized');
    }

    const reputation = await this.checkReputation(phoneNumber);
    const result = this.decisionEngine.evaluate({
      phoneNumber,
      cachedReputation: reputation,
      ...context,
    });

    return result;
  }

  // WebSocket alert server integration
  initializeAlertServer(config?: Parameters<AlertServer['constructor']>[0]): void {
    this.alertServer = new AlertServer(config);
  }

  getAlertServer(): AlertServer | undefined {
    return this.alertServer;
  }

  async broadcastDecision(phoneNumber: string, decision: DecisionResult): Promise<void> {
    if (!this.alertServer) {
      console.log(`[SpamShield] [req:${decision.requestId ?? 'unknown'}] Alert server not initialized, skipping broadcast`);
      return;
    }

    await this.alertServer.broadcastDecision(phoneNumber, decision);
  }

  // SMS Classification Service
  async classifySms(text: string): Promise<SmsClassificationResult> {
    if (!spamFeatureFlags.enableSMSClassification) {
      throw new Error('SMS Classification disabled via feature flag');
    }
    
    if (!this.smsClassifier) {
      throw new Error('SMS Classifier not initialized');
    }
    
    return await this.smsClassifier.classify(text);
  }

  getSmsClassifier(): BertSmsClassifier | undefined {
    return this.smsClassifier;
  }

  // Combined interception methods
  async interceptCall(call: IncomingCall): Promise<DecisionResult> {
    if (!spamFeatureFlags.enableCallAnalysis) {
      throw new Error('Call analysis disabled via feature flag');
    }

    const requestId = call.requestId ?? generateRequestId();
    const decision = await this.makeRealTimeDecision(call.phoneNumber, {
      callMetadata: {
        callId: call.callId,
        startTime: call.startTime,
        direction: call.direction,
        carrierInfo: { carrierType: call.carrierType, carrierSid: call.carrierSid },
      },
      ruleMatches: [],
      requestId,
    });

    await this.executeCarrierAction(
      call.carrierType,
      decision.decision.toLowerCase() as 'block' | 'flag' | 'allow',
      call.phoneNumber,
      call.carrierSid
    );

    await this.broadcastDecision(call.phoneNumber, decision);

    return { ...decision, requestId };
  }

  async interceptSms(sms: IncomingSms): Promise<DecisionResult> {
    const requestId = sms.requestId ?? generateRequestId();
    const decision = await this.makeRealTimeDecision(sms.phoneNumber, {
      smsContent: {
        messageId: sms.messageId,
        body: sms.body,
        timestamp: sms.timestamp,
        direction: sms.direction,
      },
      ruleMatches: [],
      requestId,
    });

    await this.executeCarrierAction(
      sms.carrierType,
      decision.decision.toLowerCase() as 'block' | 'flag' | 'allow',
      sms.phoneNumber,
      sms.carrierSid,
      true
    );

    await this.broadcastDecision(sms.phoneNumber, decision);

    return { ...decision, requestId };
  }

  private async logCarrierAction(
    phoneNumber: string,
    action: string,
    carrierType: CarrierType,
    sid: string,
    isSms: boolean
  ): Promise<void> {
    await prisma.spamAuditLog.create({
      data: {
        userId: 'carrier',
        phoneNumber,
        decision: action as any,
        reason: `Carrier action: ${carrierType} ${isSms ? 'SMS' : 'Call'} ${sid}`,
      },
    });
  }

  private async fetchHiyaReputation(phoneNumber: string): Promise<number> {
    if (!spamFeatureFlags.enableHiyaIntegration) {
      throw new Error('Hiya integration disabled');
    }

    return this.hiyaBreaker.execute(
      async () => {
        const url = `https://api.hiya.com/reputation/${encodeURIComponent(phoneNumber)}`;
        const response = await fetch(url, {
          method: 'GET',
          headers: {
            'Authorization': `Bearer ${process.env.HIYA_API_KEY}`,
            'Accept': 'application/json',
          },
        });

        if (!response.ok) {
          throw new Error(`Hiya API error: ${response.status} ${response.statusText}`);
        }

        const data = await response.json() as { spamScore?: number; reputation?: { score?: number } };
        const score = data.spamScore ?? data.reputation?.score ?? 0;
        return score;
      },
      () => {
        console.log('[SpamShield] Hiya fallback: circuit OPEN, returning neutral score');
        return 0.5;
      }
    );
  }

  private async fetchTruecallerReputation(phoneNumber: string): Promise<number> {
    if (!spamFeatureFlags.enableTruecallerIntegration) {
      throw new Error('Truecaller integration disabled');
    }

    return this.truecallerBreaker.execute(
      async () => {
        const url = `https://redirect.truecaller.com/api/v2-ac/absolute/${encodeURIComponent(phoneNumber)}`;
        const response = await fetch(url, {
          method: 'GET',
          headers: {
            'contentType': 'lookupNumber',
            'Authorization': `Basic ${Buffer.from(process.env.TRUECALLER_API_KEY || '').toString('base64')}`,
            'Accept': 'application/json',
          },
        });

        if (!response.ok) {
          throw new Error(`Truecaller API error: ${response.status} ${response.statusText}`);
        }

        const data = await response.json() as { spamProbability?: number; spam_type?: number };
        const probability = data.spamProbability ?? (data.spam_type ? 0.8 : 0);
        return probability;
      },
      () => {
        console.log('[SpamShield] Truecaller fallback: circuit OPEN, returning neutral score');
        return 0.5;
      }
    );
  }

  private validatePhoneNumber(phoneNumber: string): string {
    return validateE164(phoneNumber);
  }

  private async getActiveRules(): Promise<Array<{ id: string; pattern: string }>> {
    return prisma.spamRule.findMany({
      where: { isActive: true },
      select: { id: true, pattern: true },
    });
  }

  private validateMetadata(metadata: Record<string, any>): Record<string, any> {
    const metadataStr = JSON.stringify(metadata);
    
    if (metadataStr.length > metadataLimits.maxMetadataSizeBytes) {
      console.log(`[SpamShield] Metadata size ${metadataStr.length}B exceeds limit ${metadataLimits.maxMetadataSizeBytes}B, truncating`);
    }

    const entries = Object.entries(metadata);
    const truncatedEntries: [string, any][] = [];
    
    for (let i = 0; i < Math.min(entries.length, metadataLimits.maxMetadataKeys); i++) {
      const [key, value] = entries[i];
      const valueStr = String(value);
      
      if (valueStr.length > metadataLimits.maxMetadataValueSizeBytes) {
        truncatedEntries.push([key, valueStr.slice(0, metadataLimits.maxMetadataValueSizeBytes)]);
      } else {
        truncatedEntries.push([key, value]);
      }
    }

    const result = Object.fromEntries(truncatedEntries);
    const resultStr = JSON.stringify(result);

    if (resultStr.length > metadataLimits.maxMetadataSizeBytes) {
      const shrunk: Record<string, any> = {};
      let currentSize = 2;
      
      for (const [key, value] of truncatedEntries) {
        const entrySize = key.length + String(value).length + 3;
        if (currentSize + entrySize <= metadataLimits.maxMetadataSizeBytes) {
          shrunk[key] = value;
          currentSize += entrySize;
        } else {
          break;
        }
      }
      
      console.log(`[SpamShield] Metadata reduced to ${Object.keys(shrunk).length} keys to fit size limit`);
      return shrunk;
    }

    return result;
  }
}
