import ApiLog from '../models/ApiLog.js';
import { Op } from 'sequelize';
import { sanitizeLogData, truncateString, sanitizeIpAddress, sanitizeUserAgent } from '../utils/logDataSanitizer.js';

class ApiLogService {
  /**
   * Log an API request/response
   * DSGVO-konform: Personenbezogene Daten werden nur bei Fehlern geloggt und dann verschlüsselt/gekürzt
   */
  async logRequest(options) {
    try {
      const {
        userId = null,
        method,
        path,
        statusCode = null,
        requestBody = null,
        responseBody = null,
        executionTime = null,
        errorMessage = null,
        ipAddress = null,
        userAgent = null,
        logType = 'api_request',
        schedulerJobType = null
      } = options;

  // Wenn kein Statuscode übergeben wurde, behandeln wir den Logeintrag als Fehler,
  // damit Request-/Response-Bodies für Debugging/Testzwecke gespeichert werden.
  // (Historisch haben Tests/Callsites logRequest ohne statusCode genutzt.)
  const isError = statusCode === null || statusCode === undefined ? true : statusCode >= 400;
      
      // DSGVO-konform: Nur bei Fehlern Request/Response-Bodies loggen
      let sanitizedRequestBody = null;
      let sanitizedResponseBody = null;
      
      if (isError) {
        // Bei Fehlern: Sanitize personenbezogene Daten
        if (requestBody) {
          sanitizedRequestBody = sanitizeLogData(requestBody, true); // Verschlüssele sensible Daten
          // Prüfe, ob Ergebnis bereits ein String ist (sanitizeLogData kann String oder Objekt zurückgeben)
          const requestBodyStr = typeof sanitizedRequestBody === 'string' 
            ? sanitizedRequestBody 
            : JSON.stringify(sanitizedRequestBody);
          // Für Diagnosezwecke etwas großzügiger als 2000 Zeichen, aber weiterhin begrenzt.
          sanitizedRequestBody = truncateString(requestBodyStr, 64020);
        }
        
        if (responseBody) {
          sanitizedResponseBody = sanitizeLogData(responseBody, true); // Verschlüssele sensible Daten
          // Prüfe, ob Ergebnis bereits ein String ist (sanitizeLogData kann String oder Objekt zurückgeben)
          const responseBodyStr = typeof sanitizedResponseBody === 'string' 
            ? sanitizedResponseBody 
            : JSON.stringify(sanitizedResponseBody);
          sanitizedResponseBody = truncateString(responseBodyStr, 64020);
        }
      }
      // Bei Erfolg: Keine Bodies loggen (Datenminimierung)

      // IP-Adresse und User-Agent sanitizen
      const sanitizedIp = sanitizeIpAddress(ipAddress);
      const sanitizedUA = sanitizeUserAgent(userAgent);

      await ApiLog.create({
        userId,
        method,
        path,
        statusCode,
        requestBody: sanitizedRequestBody,
        responseBody: sanitizedResponseBody,
        executionTime,
        errorMessage: errorMessage ? truncateString(errorMessage, 5000) : null,
        ipAddress: sanitizedIp,
        userAgent: sanitizedUA,
        logType,
        schedulerJobType
      });
    } catch (error) {
      console.error('Error logging API request:', error);
      // Don't throw - logging failures shouldn't break the main operation
    }
  }

  /**
   * Log scheduler execution
   */
  async logSchedulerExecution(jobType, success, message, executionTime = null, errorMessage = null) {
    try {
      // Allow optional counts object in message by accepting message as string or object
      const truncate = (str, maxLen = 10000) => {
        if (!str) return null;
        const strVal = typeof str === 'string' ? str : JSON.stringify(str);
        return strVal.length > maxLen ? strVal.substring(0, maxLen) + '... (truncated)' : strVal;
      };

      // If message is an object with details, keep it; otherwise wrap into an object
      let responseObj = null;
      try {
        if (typeof message === 'object' && message !== null) {
          responseObj = message;
        } else {
          responseObj = { message };
        }
      } catch (e) {
        responseObj = { message: String(message) };
      }

      // If executionTime or errorMessage present, add to response object for visibility
      if (executionTime !== null) responseObj.executionTime = executionTime;
      if (errorMessage) responseObj.errorMessage = errorMessage;

      await ApiLog.create({
        userId: null,
        method: 'SCHEDULER',
        path: `/scheduler/${jobType}`,
        statusCode: success ? 200 : 500,
        responseBody: truncate(JSON.stringify(responseObj)),
        executionTime,
        errorMessage: truncate(errorMessage, 5000),
        logType: 'scheduler',
        schedulerJobType: jobType
      });
    } catch (error) {
      console.error('Error logging scheduler execution:', error);
    }
  }

  /**
   * Get logs with filters
   */
  async getLogs(options = {}) {
    try {
      const {
        userId = null,
        logType = null,
        method = null,
        path = null,
        statusCode = null,
        startDate = null,
        endDate = null,
        limit = 100,
        offset = 0
      } = options;

      const where = {};

      if (userId) {
        where.userId = userId;
      }

      if (logType) {
        where.logType = logType;
      }

      if (method) {
        where.method = method;
      }

      if (path) {
        // Handle "NOT:" prefix for excluding paths (e.g., "NOT:/mytischtennis")
        if (path.startsWith('NOT:')) {
          const excludePath = path.substring(4);
          where.path = { [Op.not]: { [Op.like]: `%${excludePath}%` } };
        } else {
          where.path = { [Op.like]: `%${path}%` };
        }
      }

      if (statusCode !== null) {
        where.statusCode = statusCode;
      }

      if (startDate || endDate) {
        where.createdAt = {};
        if (startDate) {
          where.createdAt[Op.gte] = new Date(startDate);
        }
        if (endDate) {
          where.createdAt[Op.lte] = new Date(endDate);
        }
      }

      const logs = await ApiLog.findAndCountAll({
        where,
        order: [['createdAt', 'DESC']],
        limit: parseInt(limit),
        offset: parseInt(offset),
        attributes: [
          'id', 'userId', 'method', 'path', 'statusCode',
          'executionTime', 'errorMessage', 'ipAddress', 'logType',
          'schedulerJobType', 'createdAt'
          // requestBody und responseBody werden NICHT zurückgegeben (DSGVO: Datenminimierung)
          // Nur bei expliziter Anfrage über getLogById verfügbar
        ]
      });

      return {
        logs: logs.rows,
        total: logs.count,
        limit: parseInt(limit),
        offset: parseInt(offset)
      };
    } catch (error) {
      console.error('Error getting logs:', error);
      throw error;
    }
  }

  /**
   * Get a single log by ID
   */
  async getLogById(logId) {
    try {
      const log = await ApiLog.findByPk(logId);
      return log;
    } catch (error) {
      console.error('Error getting log by ID:', error);
      throw error;
    }
  }

  /**
   * Get last execution info for scheduler jobs
   */
  async getLastSchedulerExecutions(clubId = null) {
    try {
      const jobTypes = ['rating_updates', 'match_results'];
      const results = {};

      for (const jobType of jobTypes) {
        const lastExecution = await ApiLog.findOne({
          where: {
            logType: 'scheduler',
            schedulerJobType: jobType
          },
          order: [['createdAt', 'DESC']],
          attributes: ['id', 'createdAt', 'statusCode', 'responseBody', 'executionTime', 'errorMessage']
        });

        if (lastExecution) {
          let parsedResponse = null;
          let updatedCount = null;
          let fetchedCount = null;
          let teamDetails = [];

          // Parse responseBody to extract counts
          try {
            if (lastExecution.responseBody) {
              parsedResponse = JSON.parse(lastExecution.responseBody);
              // Extract counts from response
              if (parsedResponse.totalUpdated !== undefined) {
                updatedCount = parsedResponse.totalUpdated;
              } else if (parsedResponse.updatedCount !== undefined) {
                updatedCount = parsedResponse.updatedCount;
              }
              if (parsedResponse.totalFetched !== undefined) {
                fetchedCount = parsedResponse.totalFetched;
              } else if (parsedResponse.fetchedCount !== undefined) {
                fetchedCount = parsedResponse.fetchedCount;
              }
            }
          } catch (e) {
            // Ignore parsing errors
          }

          // For match_results, try to get team-specific details from API logs
          if (jobType === 'match_results') {
            try {
              // Find all API logs created around the same time as the scheduler execution
              // (within 5 minutes before and after)
              const timeWindowStart = new Date(lastExecution.createdAt);
              timeWindowStart.setMinutes(timeWindowStart.getMinutes() - 5);
              const timeWindowEnd = new Date(lastExecution.createdAt);
              timeWindowEnd.setMinutes(timeWindowEnd.getMinutes() + 5);

              const teamLogs = await ApiLog.findAll({
                where: {
                  logType: 'api_request',
                  schedulerJobType: 'mytischtennis_fetch',
                  createdAt: {
                    [Op.between]: [timeWindowStart, timeWindowEnd]
                  }
                },
                order: [['createdAt', 'DESC']],
                attributes: ['id', 'requestBody', 'statusCode', 'createdAt']
              });

              // Extract team information from requestBody
              const teamMap = new Map();
              for (const log of teamLogs) {
                try {
                  if (log.requestBody) {
                    const requestData = JSON.parse(log.requestBody);
                    if (requestData.clubTeamId && requestData.teamName) {
                      const teamId = requestData.clubTeamId;
                      if (!teamMap.has(teamId)) {
                        teamMap.set(teamId, {
                          clubTeamId: teamId,
                          teamName: requestData.teamName,
                          success: log.statusCode === 200,
                          lastRun: log.createdAt
                        });
                      }
                    }
                  }
                } catch (e) {
                  // Ignore parsing errors for individual logs
                }
              }

              teamDetails = Array.from(teamMap.values());
            } catch (e) {
              console.error('Error extracting team details:', e);
            }
          }

          results[jobType] = {
            lastRun: lastExecution.createdAt,
            success: lastExecution.statusCode === 200,
            executionTime: lastExecution.executionTime,
            updatedCount: updatedCount,
            fetchedCount: fetchedCount,
            errorMessage: lastExecution.errorMessage,
            teamDetails: teamDetails
          };
        } else {
          results[jobType] = {
            lastRun: null,
            success: null,
            executionTime: null,
            updatedCount: null,
            fetchedCount: null,
            errorMessage: null,
            teamDetails: []
          };
        }
      }

      return results;
    } catch (error) {
      console.error('Error getting last scheduler executions:', error);
      throw error;
    }
  }
}

export default new ApiLogService();

