import jwt from 'jsonwebtoken';
import https from 'https';
import { loadNotificationConfig } from '../config/notification.config';
import type { PushNotification, NotificationResult } from '../types/notification.types';

const config = loadNotificationConfig();

let apnsToken: string | null = null;
let tokenExpiration: number = 0;

interface APNsPayload {
  aps: {
    alert: {
      title: string;
      body: string;
    };
    badge?: number;
    sound?: string | {
      critical: number;
      name: string;
      volume: number;
    };
    category?: string;
    'content-available'?: number;
    'mutable-content'?: number;
  };
  [key: string]: any;
}

function generateAPNSToken(): string {
  const now = Math.floor(Date.now() / 1000);
  
  const token = jwt.sign(
    {},
    config.apns.key,
    {
      algorithm: 'ES256' as any,
      headers: {
        alg: 'ES256',
        kty: 'EC',
        kid: config.apns.keyId,
      },
      expiresIn: '1h',
      issuer: config.apns.teamId,
    } as any
  );
  
  tokenExpiration = now + 3500; // Refresh 1 minute before expiration
  return token;
}

function getAPNSToken(): string {
  const now = Math.floor(Date.now() / 1000);
  
  if (!apnsToken || now > tokenExpiration) {
    apnsToken = generateAPNSToken();
  }
  
  return apnsToken;
}

export class APNSService {
  private static instance: APNSService;
  private host: string;
  
  private constructor() {
    this.host = process.env.NODE_ENV === 'production' 
      ? 'api.push.apple.com' 
      : 'api.sandbox.push.apple.com';
  }
  
  static getInstance(): APNSService {
    if (!APNSService.instance) {
      APNSService.instance = new APNSService();
    }
    return APNSService.instance;
  }
  
  async send(notification: PushNotification): Promise<NotificationResult> {
    try {
      const token = getAPNSToken();
      const payload = this.buildPayload(notification);
      const apnsToken = notification.data?.apnsToken as string || notification.userId;

      // Validate payload size (APNs limit: 4KB for alert, 256KB total)
      const payloadStr = JSON.stringify(payload);
      const payloadSize = Buffer.byteLength(payloadStr, 'utf8');
      if (payloadSize > 256 * 1024) {
        return {
          notificationId: `apns-${Date.now()}`,
          channel: 'push',
          status: 'failed',
          error: `Payload size ${payloadSize} exceeds APNs limit of 256KB`,
        };
      }
      
      const result = await this.sendToDevice(apnsToken, payload, token);
      
      return {
        notificationId: `apns-${Date.now()}`,
        channel: 'push',
        status: result.success ? 'sent' : 'failed',
        externalId: result.responseId,
        error: result.error,
        deliveredAt: result.success ? new Date() : undefined,
      };
    } catch (error) {
      return {
        notificationId: `apns-${Date.now()}`,
        channel: 'push',
        status: 'failed',
        error: error instanceof Error ? error.message : 'Unknown error',
      };
    }
  }
  
  private buildPayload(notification: PushNotification): APNsPayload {
    const payload: APNsPayload = {
      aps: {
        alert: {
          title: notification.title,
          body: notification.body,
        },
        sound: notification.sound || 'default',
      },
    };
    
    if (notification.badge !== undefined) {
      payload.aps.badge = notification.badge;
    }
    
    if (notification.category) {
      payload.aps.category = notification.category;
    }
    
    if (notification.data) {
      Object.entries(notification.data).forEach(([key, value]) => {
        if (key !== 'apnsToken') {
          payload[key] = value;
        }
      });
    }
    
    return payload;
  }
  
  private sendToDevice(
    deviceToken: string,
    payload: APNsPayload,
    authToken: string
  ): Promise<{ success: boolean; responseId?: string; error?: string }> {
    return new Promise((resolve) => {
      const url = `https://${this.host}:443/3/device/${deviceToken}`;
      
      const options = {
        hostname: this.host,
        port: 443,
        path: `/3/device/${deviceToken}`,
        method: 'POST',
        headers: {
          'Authorization': `bearer ${authToken}`,
          'Content-Type': 'application/json',
          'apns-push-type': 'alert',
          'apns-priority': '10',
          'apns-topic': config.apns.bundleId,
        },
      };
      
      const req = https.request(url, options, (res) => {
        let responseData = '';
        
        res.on('data', (chunk) => {
          responseData += chunk;
        });
        
        res.on('end', () => {
          if (res.statusCode === 200) {
            resolve({
              success: true,
              responseId: res.headers['apns-id'] as string,
            });
          } else {
            let error = `APNs error: ${res.statusCode}`;
            try {
              const errorBody = JSON.parse(responseData);
              error = errorBody.reason || errorBody.message || error;
            } catch {
              // Keep default error message
            }
            resolve({
              success: false,
              error,
            });
          }
        });
      });
      
      req.on('error', (error) => {
        resolve({
          success: false,
          error: error.message,
        });
      });
      
      req.write(JSON.stringify(payload));
      req.end();
    });
  }
  
  async sendBatch(notifications: PushNotification[]): Promise<NotificationResult[]> {
    const results = await Promise.all(
      notifications.map(n => this.send(n))
    );
    return results;
  }

  /**
   * Test APNs connection by validating configuration and making a test request
   */
  async testConnection(): Promise<{ success: boolean; message: string }> {
    try {
      // Validate required configuration
      if (!config.apns.keyId || !config.apns.teamId || !config.apns.bundleId) {
        return {
          success: false,
          message: 'Missing required APNs configuration: keyId, teamId, or bundleId',
        };
      }

      if (!config.apns.key) {
        return {
          success: false,
          message: 'Missing APNs private key',
        };
      }

      // Generate a token to verify the key is valid
      const token = getAPNSToken();
      if (!token) {
        return {
          success: false,
          message: 'Failed to generate APNs authentication token',
        };
      }

      // Make a minimal test request to APNs to verify connection
      return new Promise((resolve) => {
        const testDeviceToken = '0000000000000000000000000000000000000000000000000000000000000000';
        const url = `https://${this.host}:443/3/device/${testDeviceToken}`;
        
        const options = {
          hostname: this.host,
          port: 443,
          path: `/3/device/${testDeviceToken}`,
          method: 'POST',
          headers: {
            'Authorization': `bearer ${token}`,
            'Content-Type': 'application/json',
            'apns-push-type': 'alert',
            'apns-priority': '10',
            'apns-topic': config.apns.bundleId,
          },
          timeout: 5000,
        };

        const req = https.request(url, options, (res) => {
          // Any response means we connected successfully
          // Even 410 (device token not registered) means connection works
          if (res.statusCode && res.statusCode < 500) {
            resolve({
              success: true,
              message: `APNs connection successful (status: ${res.statusCode})`,
            });
          } else {
            resolve({
              success: false,
              message: `APNs connection failed with status: ${res.statusCode}`,
            });
          }
        });

        req.on('error', (error) => {
          // Connection errors are expected if no real APNs credentials
          resolve({
            success: false,
            message: `APNs connection error: ${error.message}`,
          });
        });

        req.on('timeout', () => {
          req.destroy();
          resolve({
            success: false,
            message: 'APNs connection timeout',
          });
        });

        // Send minimal payload
        req.write(JSON.stringify({ aps: { sound: 'default' } }));
        req.end();
      });
    } catch (error) {
      return {
        success: false,
        message: error instanceof Error ? error.message : 'Unknown error',
      };
    }
  }
}
