import winston from 'winston';
import path from 'path';
import os from 'os';

const logDir = process.env.LOG_FILE_PATH || './logs';
const isProduction = process.env.NODE_ENV === 'production';

// ============ SENSITIVE DATA PATTERNS ============
const SENSITIVE_KEYS = [
  'password', 'secret', 'token', 'key', 'apikey', 'api_key', 'api-key',
  'authorization', 'auth', 'credential', 'private', 'access_token',
  'refresh_token', 'jwt', 'bearer', 'session', 'cookie', 'csrf',
  'vapid', 'smtp_password', 'db_password', 'redis_url', 'connection_string'
];

const SENSITIVE_PATTERN = new RegExp(
  `(${SENSITIVE_KEYS.join('|')})\\s*[=:]\\s*['"]?([^'\"\\s,}\\]]+)['"]?`,
  'gi'
);

/**
 * Mask sensitive values in strings (API keys, passwords, tokens)
 * Uses a WeakSet to prevent infinite recursion on circular objects
 */
const maskSensitiveData = (value: any, visited = new WeakSet()): any => {
  if (typeof value === 'string') {
    // Mask patterns like "API_KEY = abc123" or "password: secret"
    let masked = value.replace(SENSITIVE_PATTERN, (match, key, val) => {
      if (val && val.length > 0) {
        const maskedVal = val.length > 4
          ? val.substring(0, 2) + '***' + val.substring(val.length - 2)
          : '***';
        return `${key}=${maskedVal}`;
      }
      return match;
    });

    // Mask standalone tokens/keys (long alphanumeric strings that look like secrets)
    // e.g., "sk-abc123xyz789..." or "ghp_xxxx..."
    masked = masked.replace(
      /\b(sk-|ghp_|gho_|github_pat_|xox[baprs]-|Bearer\s+)([A-Za-z0-9_-]{20,})/gi,
      (match, prefix, token) => `${prefix}${'*'.repeat(8)}...`
    );

    return masked;
  }

  if (Array.isArray(value)) {
    if (visited.has(value)) return '[Circular]';
    visited.add(value);
    return value.map(item => maskSensitiveData(item, visited));
  }

  if (value && typeof value === 'object') {
    // Special handling for common non-recursive objects to improve performance
    if (value instanceof Date || value instanceof RegExp) {
      return value;
    }

    if (visited.has(value)) return '[Circular]';
    visited.add(value);

    // Prevent deep recursion into huge circular objects like Sequelize instances, Request, Response
    // If it looks like a Sequelize instance or complex object, we might want to be careful
    // but visited.has() should handle it.

    const masked: any = {};
    for (const [k, v] of Object.entries(value)) {
      const keyLower = k.toLowerCase();
      // Check if key itself is sensitive
      if (SENSITIVE_KEYS.some(sk => keyLower.includes(sk))) {
        masked[k] = typeof v === 'string' && v.length > 0 ? '***REDACTED***' : v;
      } else {
        masked[k] = maskSensitiveData(v, visited);
      }
    }
    return masked;
  }

  return value;
};

// ============ COMMON LABELS/METADATA ============
const appMeta = {
  app: 're-workflow',
  service: 'backend',
  environment: process.env.NODE_ENV || 'development',
  version: process.env.APP_VERSION || '1.2.0',
};

// ============ TRANSPORTS ============
const transports: winston.transport[] = [
  // Local file transport - Error logs
  new winston.transports.File({
    filename: path.join(logDir, 'error.log'),
    level: 'error',
    maxsize: 10 * 1024 * 1024, // 10MB
    maxFiles: 10,
    tailable: true,
  }),
  // Local file transport - Combined logs
  new winston.transports.File({
    filename: path.join(logDir, 'combined.log'),
    maxsize: 10 * 1024 * 1024, // 10MB
    maxFiles: 10,
    tailable: true,
  }),
];

// ============ LOKI TRANSPORT (Grafana) ============
// Only enable Loki in production (or LOKI_FORCE=true); in dev, connection errors are noisy when Loki isn't running
if (process.env.LOKI_HOST && (isProduction || process.env.LOKI_FORCE === 'true')) {
  try {
    const LokiTransport = require('winston-loki');

    const lokiTransportOptions: any = {
      host: process.env.LOKI_HOST,
      labels: appMeta,
      json: true,
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
      ),
      replaceTimestamp: true,
      onConnectionError: (err: Error) => {
        if (isProduction) console.error('[Loki] Connection error:', err.message);
      },
      batching: true,
      interval: 5,
    };

    if (process.env.LOKI_USER && process.env.LOKI_PASSWORD) {
      lokiTransportOptions.basicAuth = `${process.env.LOKI_USER}:${process.env.LOKI_PASSWORD}`;
    }

    transports.push(new LokiTransport(lokiTransportOptions));
  } catch (error) {
    console.warn('[Logger] ⚠️ Failed to initialize Loki transport:', (error as Error).message);
  }
}

// ============ CONSOLE TRANSPORT ============
// Enabled for all environments to ensure visibility in terminal/container logs
transports.push(
  new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize({ all: !isProduction }),
      winston.format.printf(({ level, message, timestamp, ...meta }) => {
        const metaStr = Object.keys(meta).length && !meta.service
          ? ` ${JSON.stringify(meta)}`
          : '';
        return `${timestamp} [${level}]: ${message}${metaStr}`;
      })
    ),
  })
);

// ============ ERROR SANITIZER ============
/**
 * Sanitize error objects for logging - prevents huge Axios error dumps
 */
const sanitizeError = (error: any): object => {
  // Handle Axios errors specifically
  if (error?.isAxiosError || error?.name === 'AxiosError') {
    return {
      name: error.name,
      message: error.message,
      code: error.code,
      status: error.response?.status,
      statusText: error.response?.statusText,
      url: error.config?.url,
      method: error.config?.method,
      responseData: error.response?.data,
    };
  }

  // Handle standard errors
  if (error instanceof Error) {
    return {
      name: error.name,
      message: error.message,
      stack: error.stack,
      errors: (error as any).errors,
      ...(error as any).statusCode && { statusCode: (error as any).statusCode },
    };
  }

  // Fallback for unknown error types
  return {
    message: String(error),
    type: typeof error,
  };
};

// Custom format to sanitize errors and mask sensitive data before logging
const sanitizeFormat = winston.format((info) => {
  // Sanitize error objects
  if (info.error && typeof info.error === 'object') {
    info.error = sanitizeError(info.error);
  }

  // If message is an error object, sanitize it
  if (info.message && typeof info.message === 'object' && (info.message as any).stack) {
    info.error = sanitizeError(info.message);
    info.message = (info.message as Error).message;
  }

  // Mask sensitive data in message
  if (typeof info.message === 'string') {
    info.message = maskSensitiveData(info.message);
  }

  // Mask sensitive data in all metadata
  for (const key of Object.keys(info)) {
    if (key !== 'level' && key !== 'timestamp' && key !== 'service') {
      info[key] = maskSensitiveData(info[key]);
    }
  }

  return info;
});

// ============ CREATE LOGGER ============
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }),
    sanitizeFormat(),
    winston.format.json()
  ),
  defaultMeta: {
    service: 're-workflow-backend',
    hostname: os.hostname(),
  },
  transports,
});

// ============ HELPER METHODS FOR STRUCTURED LOGGING ============

/**
 * Log with additional context labels (will appear in Grafana)
 */
export const logWithContext = (
  level: 'info' | 'warn' | 'error' | 'debug',
  message: string,
  context: {
    // Domain labels
    requestId?: string;
    userId?: string;
    priority?: 'STANDARD' | 'EXPRESS';
    status?: string;
    department?: string;
    // API labels  
    endpoint?: string;
    method?: string;
    statusCode?: number;
    duration?: number;
    // Error context
    errorType?: string;
    error?: any;
    stack?: string;
    // Custom data
    [key: string]: any;
  }
) => {
  // Sanitize error if present
  const sanitizedContext = { ...context };
  if (sanitizedContext.error) {
    sanitizedContext.error = sanitizeError(sanitizedContext.error);
  }

  logger.log(level, message, sanitizedContext);
};

/**
 * Log API request (use in middleware or controllers)
 */
export const logApiRequest = (
  method: string,
  endpoint: string,
  statusCode: number,
  duration: number,
  userId?: string,
  error?: string
) => {
  const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
  logger.log(level, `${method} ${endpoint} ${statusCode} ${duration}ms`, {
    endpoint,
    method,
    statusCode,
    duration,
    userId,
    ...(error && { error }),
  });
};

/**
 * Log workflow events
 */
export const logWorkflowEvent = (
  event: 'created' | 'submitted' | 'approved' | 'rejected' | 'closed' | 'paused' | 'resumed' | 'updated',
  requestId: string,
  details: {
    priority?: string;
    status?: string;
    department?: string;
    userId?: string;
    userName?: string;
    message?: string;
    level?: number;
    [key: string]: any;
  } = {}
) => {
  logger.info(`Workflow ${event}: ${requestId}`, {
    workflowEvent: event,
    requestId,
    ...details,
  });
};

/**
 * Log TAT/SLA events
 */
export const logTATEvent = (
  event: 'approaching' | 'breached' | 'resolved' | 'warning',
  requestId: string,
  details: {
    priority?: string;
    threshold?: number;
    elapsedHours?: number;
    tatHours?: number;
    level?: number;
    [key: string]: any;
  } = {}
) => {
  const level = event === 'breached' ? 'error' : event === 'approaching' || event === 'warning' ? 'warn' : 'info';
  logger.log(level, `TAT ${event}: ${requestId}`, {
    tatEvent: event,
    requestId,
    ...details,
  });
};

/**
 * Log authentication events
 */
export const logAuthEvent = (
  event: 'login' | 'logout' | 'token_refresh' | 'token_exchange' | 'auth_failure' | 'sso_callback',
  userId: string | undefined,
  details: {
    email?: string;
    role?: string;
    ip?: string;
    userAgent?: string;
    error?: any;
    [key: string]: any;
  } = {}
) => {
  const level = event === 'auth_failure' ? 'warn' : 'info';

  // Sanitize error if present
  const sanitizedDetails = { ...details };
  if (sanitizedDetails.error) {
    sanitizedDetails.error = sanitizeError(sanitizedDetails.error);
  }

  logger.log(level, `Auth ${event}${userId ? `: ${userId}` : ''}`, {
    authEvent: event,
    userId,
    ...sanitizedDetails,
  });
};

/**
 * Log document events
 */
export const logDocumentEvent = (
  event: 'uploaded' | 'downloaded' | 'deleted' | 'previewed',
  documentId: string,
  details: {
    requestId?: string;
    userId?: string;
    fileName?: string;
    fileType?: string;
    fileSize?: number;
    [key: string]: any;
  } = {}
) => {
  logger.info(`Document ${event}: ${documentId}`, {
    documentEvent: event,
    documentId,
    ...details,
  });
};

/**
 * Log notification events
 */
export const logNotificationEvent = (
  event: 'sent' | 'failed' | 'queued',
  details: {
    type?: string;
    userId?: string;
    requestId?: string;
    channel?: 'push' | 'email' | 'in-app';
    error?: any;
    [key: string]: any;
  } = {}
) => {
  const level = event === 'failed' ? 'error' : 'info';

  // Sanitize error if present
  const sanitizedDetails = { ...details };
  if (sanitizedDetails.error) {
    sanitizedDetails.error = sanitizeError(sanitizedDetails.error);
  }

  logger.log(level, `Notification ${event}`, {
    notificationEvent: event,
    ...sanitizedDetails,
  });
};

/**
 * Log AI service events
 */
export const logAIEvent = (
  event: 'request' | 'response' | 'error' | 'fallback',
  details: {
    provider?: string;
    model?: string;
    requestId?: string;
    duration?: number;
    error?: any;
    [key: string]: any;
  } = {}
) => {
  const level = event === 'error' ? 'error' : 'info';

  // Sanitize error if present
  const sanitizedDetails = { ...details };
  if (sanitizedDetails.error) {
    sanitizedDetails.error = sanitizeError(sanitizedDetails.error);
  }

  logger.log(level, `AI ${event}`, {
    aiEvent: event,
    ...sanitizedDetails,
  });
};

// ============ MORGAN STREAM ============
// Create a stream object for Morgan HTTP logging
const loggerWithStream = logger as any;
loggerWithStream.stream = {
  write: (message: string) => {
    logger.info(message.trim());
  },
};

// Export helper functions and logger
export { sanitizeError };
export default loggerWithStream as winston.Logger;
