import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { SpamShieldService } from '../src/services/spamshield.service';
import { spamConfig, checkFeatureFlag, spamFeatureFlags, spamFeatureFlagDefaults } from '../src/config/spamshield.config';
import { validatePhoneNumber } from '../src/utils/phone-validation';

const mockFetch = vi.fn();
global.fetch = mockFetch as unknown as typeof global.fetch;

describe('SpamShieldService', () => {
  let service: SpamShieldService;

  beforeEach(() => {
    service = SpamShieldService.getInstance();
    service.resetCircuits();
    mockFetch.mockReset();
    vi.clearAllMocks();
  });

  describe('checkReputation', () => {
    it('combines scores from both Hiya and Truecaller', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ spamScore: 0.8 }),
        } as Response)
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ spamProbability: 0.9 }),
        } as Response);

      const result = await service.checkReputation('+14155552671');

      expect(result.source).toBe('combined');
      expect(result.score).toBeCloseTo(0.85, 2);
      expect(result.isSpam).toBe(true);
      expect(result.hiyaScore).toBe(0.8);
      expect(result.truecallerScore).toBe(0.9);
      expect(mockFetch).toHaveBeenCalledTimes(2);
    });

    it('uses Hiya score + Truecaller fallback when Truecaller API fails', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ spamScore: 0.6 }),
        } as Response)
        .mockResolvedValueOnce({
          ok: false,
          status: 500,
          statusText: 'Internal Server Error',
        } as Response);

      const result = await service.checkReputation('+14155552671');

      expect(result.source).toBe('combined');
      expect(result.hiyaScore).toBe(0.6);
      expect(result.truecallerScore).toBe(0.5);
    });

    it('uses Hiya fallback + Truecaller score when Hiya API fails', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: false,
          status: 429,
          statusText: 'Too Many Requests',
        } as Response)
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ spamProbability: 0.3 }),
        } as Response);

      const result = await service.checkReputation('+14155552671');

      expect(result.source).toBe('combined');
      expect(result.hiyaScore).toBe(0.5);
      expect(result.truecallerScore).toBe(0.3);
    });

    it('uses both fallbacks when both APIs fail', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: false,
          status: 503,
          statusText: 'Service Unavailable',
        } as Response)
        .mockResolvedValueOnce({
          ok: false,
          status: 503,
          statusText: 'Service Unavailable',
        } as Response);

      const result = await service.checkReputation('+14155552671');

      expect(result.source).toBe('combined');
      expect(result.hiyaScore).toBe(0.5);
      expect(result.truecallerScore).toBe(0.5);
      expect(result.score).toBe(0.5);
      expect(result.isSpam).toBe(false);
    });

    it('validates phone number E.164 format', async () => {
      const shortNumber = '123';
      const result = service.checkReputation(shortNumber);
      await expect(result).rejects.toThrow('Invalid E.164 phone number format');
    });

    it('returns non-spam for low scores', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ spamScore: 0.2 }),
        } as Response)
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ spamProbability: 0.1 }),
        } as Response);

      const result = await service.checkReputation('+14155552671');

      expect(result.isSpam).toBe(false);
      expect(result.score).toBeCloseTo(0.15, 2);
    });
  });

  describe('circuit breaker integration', () => {
    it('opens circuit after consecutive failures', async () => {
      const metricsBefore = service.getCircuitMetrics();
      expect(metricsBefore.hiya.state).toBe('CLOSED');

      for (let i = 0; i < spamConfig.circuitBreakerThreshold; i++) {
        mockFetch
          .mockResolvedValueOnce({
            ok: false,
            status: 500,
            statusText: 'Server Error',
          } as Response)
          .mockResolvedValueOnce({
            ok: false,
            status: 500,
            statusText: 'Server Error',
          } as Response);
        await service.checkReputation('+14155552671');
      }

      const metricsAfter = service.getCircuitMetrics();
      expect(metricsAfter.hiya.totalFailures).toBeGreaterThanOrEqual(spamConfig.circuitBreakerThreshold);
      expect(metricsAfter.truecaller.totalFailures).toBeGreaterThanOrEqual(spamConfig.circuitBreakerThreshold);
    });

    it('exposes circuit metrics for monitoring', () => {
      const metrics = service.getCircuitMetrics();

      expect(metrics.hiya).toHaveProperty('state', 'CLOSED');
      expect(metrics.hiya).toHaveProperty('failureCount');
      expect(metrics.hiya).toHaveProperty('successCount');
      expect(metrics.hiya).toHaveProperty('totalExecutions');
      expect(metrics.hiya).toHaveProperty('totalFailures');
      expect(metrics.hiya).toHaveProperty('totalSuccesses');
      expect(metrics.hiya).toHaveProperty('lastFailureTime');
      expect(metrics.hiya).toHaveProperty('lastSuccessTime');
      expect(metrics.hiya).toHaveProperty('stateChangedAt');

      expect(metrics.truecaller).toHaveProperty('state', 'CLOSED');
    });

    it('resets circuits to CLOSED state', () => {
      service.resetCircuits();
      const metrics = service.getCircuitMetrics();
      expect(metrics.hiya.state).toBe('CLOSED');
      expect(metrics.truecaller.state).toBe('CLOSED');
    });

    it('returns fallback scores when circuits are open', async () => {
      service.resetCircuits();

      for (let i = 0; i < spamConfig.circuitBreakerThreshold; i++) {
        mockFetch
          .mockResolvedValueOnce({
            ok: false,
            status: 500,
            statusText: 'Server Error',
          } as Response)
          .mockResolvedValueOnce({
            ok: false,
            status: 500,
            statusText: 'Server Error',
          } as Response);
        await service.checkReputation('+14155552671');
      }

      const metrics = service.getCircuitMetrics();
      expect(metrics.hiya.state).toBe('OPEN');
      expect(metrics.truecaller.state).toBe('OPEN');

      const result = await service.checkReputation('+14155552671');
      expect(result.hiyaScore).toBe(0.5);
      expect(result.truecallerScore).toBe(0.5);
    });
  });

  describe('E.164 phone number validation', () => {
    describe('valid E.164 formats', () => {
      it('accepts US number in E.164 format', () => {
        expect(() => validatePhoneNumber('+14155552671')).not.toThrow();
      });

      it('accepts UK number in E.164 format', () => {
        expect(() => validatePhoneNumber('+442071234567')).not.toThrow();
      });

      it('accepts German number in E.164 format', () => {
        expect(() => validatePhoneNumber('+4930123456789')).not.toThrow();
      });

      it('accepts Japanese number in E.164 format', () => {
        expect(() => validatePhoneNumber('+81312345678')).not.toThrow();
      });

      it('accepts Australian number in E.164 format', () => {
        expect(() => validatePhoneNumber('+61412345678')).not.toThrow();
      });

      it('accepts Indian number in E.164 format', () => {
        expect(() => validatePhoneNumber('+919876543210')).not.toThrow();
      });

      it('accepts Brazilian number in E.164 format', () => {
        expect(() => validatePhoneNumber('+5511987654321')).not.toThrow();
      });

      it('accepts number with spaces and normalizes', () => {
        const result = validatePhoneNumber('+1 415 555 2671');
        expect(result).toBe('+14155552671');
      });

      it('accepts number with dashes and normalizes', () => {
        const result = validatePhoneNumber('+1-415-555-2671');
        expect(result).toBe('+14155552671');
      });

      it('accepts number with parentheses and normalizes', () => {
        const result = validatePhoneNumber('+1 (415) 555-2671');
        expect(result).toBe('+14155552671');
      });

      it('trims whitespace from input', () => {
        expect(() => validatePhoneNumber('  +14155552671  ')).not.toThrow();
      });
    });

    describe('invalid E.164 formats', () => {
      it('rejects number without plus sign', () => {
        expect(() => validatePhoneNumber('14155552671')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects number with letters', () => {
        expect(() => validatePhoneNumber('+1415555ABCD')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects empty string', () => {
        expect(() => validatePhoneNumber('')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects too-short number', () => {
        expect(() => validatePhoneNumber('+123')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects number exceeding 15 digits', () => {
        expect(() => validatePhoneNumber('+1234567890123456')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects number with special characters in middle', () => {
        expect(() => validatePhoneNumber('+1@4155552671')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects double plus sign', () => {
        expect(() => validatePhoneNumber('++14155552671')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects only whitespace', () => {
        expect(() => validatePhoneNumber('   ')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects number starting with zero after plus', () => {
        expect(() => validatePhoneNumber('+01234567890')).toThrow('Invalid E.164 phone number format');
      });

      it('rejects negative number format', () => {
        expect(() => validatePhoneNumber('-14155552671')).toThrow('Invalid E.164 phone number format');
      });
    });

    describe('integration with service methods', () => {
      it('rejects invalid format in checkReputation', async () => {
        await expect(service.checkReputation('4155552671')).rejects.toThrow('Invalid E.164 phone number format');
      });

      it('rejects invalid format in analyzeCall', async () => {
        const result = service.analyzeCall('4155552671', new Date());
        await expect(result).rejects.toThrow('Invalid E.164 phone number format');
      });

      it('rejects invalid format in recordFeedback', async () => {
        const result = service.recordFeedback('user123', '4155552671', true);
        await expect(result).rejects.toThrow('Invalid E.164 phone number format');
      });
    });
  });

  describe('feature flags', () => {
    afterEach(() => {
      for (const key of ['FLAG_ENABLEHIYAINTEGRATION', 'FLAG_ENABLETRUECALLERINTEGRATION', 'FLAG_ENABLESMSCLASSIFICATION', 'FLAG_ENABLECALLANALYSIS', 'FLAG_ENABLEFEEDBACKLOOP']) {
        delete process.env[key];
      }
    });

    describe('checkFeatureFlag', () => {
      it('returns default true when env var is unset', () => {
        expect(checkFeatureFlag('enableHiyaIntegration')).toBe(true);
        expect(checkFeatureFlag('enableCallAnalysis')).toBe(true);
        expect(checkFeatureFlag('enableFeedbackLoop')).toBe(true);
      });

      it('returns true when env var is "true"', () => {
        process.env.FLAG_ENABLECALLANALYSIS = 'true';
        expect(checkFeatureFlag('enableCallAnalysis')).toBe(true);
      });

      it('returns true when env var is "1"', () => {
        process.env.FLAG_ENABLECALLANALYSIS = '1';
        expect(checkFeatureFlag('enableCallAnalysis')).toBe(true);
      });

      it('returns false when env var is "false"', () => {
        process.env.FLAG_ENABLECALLANALYSIS = 'false';
        expect(checkFeatureFlag('enableCallAnalysis')).toBe(false);
      });

      it('returns false when env var is "0"', () => {
        process.env.FLAG_ENABLECALLANALYSIS = '0';
        expect(checkFeatureFlag('enableCallAnalysis')).toBe(false);
      });

      it('returns false for any non-true/non-1 value', () => {
        process.env.FLAG_ENABLECALLANALYSIS = 'yes';
        expect(checkFeatureFlag('enableCallAnalysis')).toBe(false);
      });
    });

    describe('spamFeatureFlags getters', () => {
      it('reflects env var overrides for enableCallAnalysis', () => {
        expect(spamFeatureFlags.enableCallAnalysis).toBe(true);
        process.env.FLAG_ENABLECALLANALYSIS = 'false';
        expect(spamFeatureFlags.enableCallAnalysis).toBe(false);
      });

      it('reflects env var overrides for enableFeedbackLoop', () => {
        expect(spamFeatureFlags.enableFeedbackLoop).toBe(true);
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'false';
        expect(spamFeatureFlags.enableFeedbackLoop).toBe(false);
      });

      it('reflects env var overrides for enableHiyaIntegration', () => {
        expect(spamFeatureFlags.enableHiyaIntegration).toBe(true);
        process.env.FLAG_ENABLEHIYAINTEGRATION = 'false';
        expect(spamFeatureFlags.enableHiyaIntegration).toBe(false);
      });

      it('reflects env var overrides for enableTruecallerIntegration', () => {
        expect(spamFeatureFlags.enableTruecallerIntegration).toBe(true);
        process.env.FLAG_ENABLETRUECALLERINTEGRATION = 'false';
        expect(spamFeatureFlags.enableTruecallerIntegration).toBe(false);
      });

      it('reflects env var overrides for enableSMSClassification', () => {
        expect(spamFeatureFlags.enableSMSClassification).toBe(true);
        process.env.FLAG_ENABLESMSCLASSIFICATION = 'false';
        expect(spamFeatureFlags.enableSMSClassification).toBe(false);
      });
    });

    describe('enableCallAnalysis flag', () => {
      it('throws when call analysis is disabled in analyzeCall', async () => {
        process.env.FLAG_ENABLECALLANALYSIS = 'false';
        const result = service.analyzeCall('+14155552671', new Date());
        await expect(result).rejects.toThrow('Call analysis disabled via feature flag');
      });

      it('throws when call analysis is disabled in interceptCall', async () => {
        process.env.FLAG_ENABLECALLANALYSIS = 'false';
        const call = {
          callId: 'call-1',
          phoneNumber: '+14155552671',
          from: '+14155552671',
          to: '+14155551234',
          startTime: new Date(),
          direction: 'inbound' as const,
          carrierType: 'twilio' as any,
          carrierSid: 'CA123',
        };
        const result = service.interceptCall(call);
        await expect(result).rejects.toThrow('Call analysis disabled via feature flag');
      });

      it('passes flag check when call analysis is enabled', async () => {
        process.env.FLAG_ENABLECALLANALYSIS = 'true';
        const result = service.analyzeCall('+14155552671', new Date());
        try {
          const res = await result;
          expect(res).toMatchObject({
            decision: 'ALLOW',
            confidence: 0,
            ruleMatches: [],
          });
        } catch (e) {
          expect((e as Error).message).not.toContain('Call analysis disabled');
        }
      });
    });

    describe('recordFeedback null checks', () => {
      it('throws when userId is null', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback(null as any, '+14155552671', true);
        await expect(result).rejects.toThrow('Feedback: userId is required');
      });

      it('throws when userId is empty string', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback('', '+14155552671', true);
        await expect(result).rejects.toThrow('Feedback: userId is required');
      });

      it('throws when phoneNumber is null', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback('user123', null as any, true);
        await expect(result).rejects.toThrow('Feedback: phoneNumber must be a non-empty string');
      });

      it('throws when isSpam is null', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback('user123', '+14155552671', null as any);
        await expect(result).rejects.toThrow('Feedback: isSpam must be a boolean');
      });

      it('throws when isSpam is undefined', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback('user123', '+14155552671', undefined as any);
        await expect(result).rejects.toThrow('Feedback: isSpam must be a boolean');
      });

      it('throws when userId is undefined', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback(undefined as any, '+14155552671', true);
        await expect(result).rejects.toThrow('Feedback: userId is required');
      });

      it('throws when phoneNumber is undefined', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback('user123', undefined as any, true);
        await expect(result).rejects.toThrow('Feedback: phoneNumber must be a non-empty string');
      });

      it('handles null metadata gracefully (falls back to default)', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback('user123', '+14155552671', true, undefined, null as any);
        try {
          await result;
        } catch (e) {
          expect((e as Error).message).not.toContain('userId is required');
          expect((e as Error).message).not.toContain('isSpam must be a boolean');
        }
      });
    });

    describe('enableFeedbackLoop flag', () => {
      it('throws when feedback loop is disabled in recordFeedback', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'false';
        const result = service.recordFeedback('user123', '+14155552671', true);
        await expect(result).rejects.toThrow('Feedback loop disabled via feature flag');
      });

      it('passes flag check when feedback loop is enabled', async () => {
        process.env.FLAG_ENABLEFEEDBACKLOOP = 'true';
        const result = service.recordFeedback('user123', '+14155552671', true);
        try {
          await result;
        } catch (e) {
          expect((e as Error).message).not.toContain('Feedback loop disabled');
        }
      });
    });

    describe('enableHiyaIntegration flag', () => {
      it('excludes Hiya score when integration is disabled', async () => {
        service.resetCircuits();
        process.env.FLAG_ENABLEHIYAINTEGRATION = 'false';
        mockFetch
          .mockResolvedValueOnce({
            ok: true,
            json: async () => ({ spamProbability: 0.3 }),
          } as Response);
        const result = await service.checkReputation('+14155552671');
        expect(result.hiyaScore).toBe(undefined);
        expect(result.source).toBe('truecaller');
      });
    });

    describe('enableTruecallerIntegration flag', () => {
      it('excludes Truecaller score when integration is disabled', async () => {
        service.resetCircuits();
        process.env.FLAG_ENABLETRUECALLERINTEGRATION = 'false';
        mockFetch
          .mockResolvedValueOnce({
            ok: true,
            json: async () => ({ spamScore: 0.8 }),
          } as Response);
        const result = await service.checkReputation('+14155552671');
        expect(result.source).toBe('hiya');
        expect(result.truecallerScore).toBe(undefined);
      });
    });

    describe('enableSMSClassification flag', () => {
      it('throws when SMS classification is disabled in classifySms', async () => {
        process.env.FLAG_ENABLESMSCLASSIFICATION = 'false';
        const result = service.classifySms('test message');
        await expect(result).rejects.toThrow('SMS Classification disabled via feature flag');
      });
    });
  });
});
