import { Injectable, Logger } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import type {
  RejectionContext,
  RejectionResult,
  EscalationConfig,
  EscalationRule,
  FailureSummary,
} from "./interfaces";

@Injectable()
export class RejectionHandlerService {
  private readonly logger = new Logger(RejectionHandlerService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Handle a rejected task
   */
  async handleRejection(context: RejectionContext): Promise<RejectionResult> {
    this.logger.warn(
      `Handling rejection for task ${context.taskId} after ${String(context.attemptCount)} attempts`
    );

    // Log rejection to database
    await this.logRejection(context);

    // Get escalation config
    const config = this.getDefaultEscalationConfig();

    // Determine escalation actions
    const escalationRules = this.determineEscalation(context, config);

    // Execute escalation
    const notificationsSent: string[] = [];
    if (escalationRules.length > 0) {
      await this.executeEscalation(context, escalationRules);
      // Collect notification targets
      escalationRules.forEach((rule) => {
        if (rule.action === "notify" && rule.target) {
          notificationsSent.push(rule.target);
        }
      });
    }

    // Determine task state based on escalation
    const taskState = this.determineTaskState(escalationRules);

    // Check if manual review is required
    const manualReviewRequired =
      context.attemptCount >= 3 ||
      escalationRules.some((r) => r.action === "block" || r.priority === "critical");

    if (manualReviewRequired) {
      await this.markForManualReview(
        context.taskId,
        `Max attempts (${String(context.attemptCount)}) exceeded or critical failure detected`
      );
    }

    return {
      handled: true,
      escalated: escalationRules.length > 0,
      notificationsSent,
      taskState,
      manualReviewRequired,
    };
  }

  /**
   * Log rejection to database
   */
  async logRejection(context: RejectionContext): Promise<void> {
    await this.prisma.taskRejection.create({
      data: {
        taskId: context.taskId,
        workspaceId: context.workspaceId,
        agentId: context.agentId,
        attemptCount: context.attemptCount,
        failures: context.failures as unknown as Prisma.InputJsonValue,
        originalTask: context.originalTask,
        startedAt: context.startedAt,
        rejectedAt: context.rejectedAt,
        escalated: false,
        manualReview: false,
      },
    });

    this.logger.log(`Logged rejection for task ${context.taskId} to database`);
  }

  /**
   * Determine escalation actions
   */
  determineEscalation(context: RejectionContext, config: EscalationConfig): EscalationRule[] {
    const applicableRules: EscalationRule[] = [];

    // Check each rule condition
    for (const rule of config.rules) {
      if (this.checkRuleCondition(context, rule, config)) {
        applicableRules.push(rule);
      }
    }

    return applicableRules;
  }

  /**
   * Check if a rule condition is met
   */
  private checkRuleCondition(
    context: RejectionContext,
    rule: EscalationRule,
    config: EscalationConfig
  ): boolean {
    switch (rule.condition) {
      case "max-attempts":
        return context.attemptCount >= 3;

      case "time-exceeded": {
        const durationMinutes =
          (context.rejectedAt.getTime() - context.startedAt.getTime()) / (1000 * 60);
        return durationMinutes > config.maxWaitTime;
      }

      case "critical-failure":
        return context.failures.some(
          (f) =>
            f.failureType.includes("critical") ||
            f.failureType.includes("security") ||
            f.failureType.includes("vulnerability")
        );

      default:
        return false;
    }
  }

  /**
   * Execute escalation rules
   */
  async executeEscalation(context: RejectionContext, rules: EscalationRule[]): Promise<void> {
    for (const rule of rules) {
      this.logger.warn(
        `Executing escalation: ${rule.action} for ${rule.condition} (priority: ${rule.priority})`
      );

      switch (rule.action) {
        case "notify":
          if (rule.target) {
            this.sendNotification(context, rule.target, rule.priority);
          }
          break;

        case "block":
          await this.markForManualReview(context.taskId, `Task blocked due to ${rule.condition}`);
          break;

        case "reassign":
          this.logger.warn(`Task ${context.taskId} marked for reassignment`);
          // Future: implement reassignment logic
          break;

        case "cancel":
          this.logger.warn(`Task ${context.taskId} marked for cancellation`);
          // Future: implement cancellation logic
          break;
      }
    }
  }

  /**
   * Send rejection notification
   */
  sendNotification(context: RejectionContext, target: string, priority: string): void {
    const report = this.generateRejectionReport(context);

    this.logger.warn(
      `[${priority.toUpperCase()}] Sending rejection notification to ${target} for task ${context.taskId}`
    );
    this.logger.debug(`Notification content:\n${report}`);

    // Future: integrate with notification service (email, Slack, etc.)
    // For now, just log the notification
  }

  /**
   * Mark task as requiring manual review
   */
  async markForManualReview(taskId: string, reason: string): Promise<void> {
    // Update the most recent rejection record for this task
    const rejections = await this.prisma.taskRejection.findMany({
      where: { taskId },
      orderBy: { rejectedAt: "desc" },
      take: 1,
    });

    if (rejections.length > 0 && rejections[0]) {
      await this.prisma.taskRejection.update({
        where: { id: rejections[0].id },
        data: {
          manualReview: true,
          escalated: true,
        },
      });

      this.logger.warn(`Task ${taskId} marked for manual review: ${reason}`);
    }
  }

  /**
   * Get rejection history for a task
   */
  async getRejectionHistory(taskId: string): Promise<RejectionContext[]> {
    const rejections = await this.prisma.taskRejection.findMany({
      where: { taskId },
      orderBy: { rejectedAt: "desc" },
    });

    return rejections.map((r) => ({
      taskId: r.taskId,
      workspaceId: r.workspaceId,
      agentId: r.agentId,
      attemptCount: r.attemptCount,
      failures: r.failures as unknown as FailureSummary[],
      originalTask: r.originalTask,
      startedAt: r.startedAt,
      rejectedAt: r.rejectedAt,
    }));
  }

  /**
   * Generate rejection report
   */
  generateRejectionReport(context: RejectionContext): string {
    const duration = this.formatDuration(context.startedAt, context.rejectedAt);

    const failureList = context.failures
      .map((f) => `- **${f.gateName}**: ${f.message} (${String(f.attempts)} attempts)`)
      .join("\n");

    const recommendations = this.generateRecommendations(context.failures);

    return `
## Task Rejection Report

**Task ID:** ${context.taskId}
**Workspace:** ${context.workspaceId}
**Agent:** ${context.agentId}
**Attempts:** ${String(context.attemptCount)}
**Duration:** ${duration}
**Started:** ${context.startedAt.toISOString()}
**Rejected:** ${context.rejectedAt.toISOString()}

### Original Task
${context.originalTask}

### Failures
${failureList}

### Required Actions
- Manual code review required
- Fix the following issues before reassigning
- Review agent output and error logs

### Recommendations
${recommendations}

---
*This report was generated automatically by the Quality Rails rejection handler.*
`;
  }

  /**
   * Get default escalation config
   */
  getDefaultEscalationConfig(): EscalationConfig {
    return {
      rules: [
        {
          condition: "max-attempts",
          action: "notify",
          target: "admin@mosaicstack.dev",
          priority: "high",
        },
        {
          condition: "max-attempts",
          action: "block",
          priority: "high",
        },
        {
          condition: "critical-failure",
          action: "notify",
          target: "security@mosaicstack.dev",
          priority: "critical",
        },
        {
          condition: "critical-failure",
          action: "block",
          priority: "critical",
        },
        {
          condition: "time-exceeded",
          action: "notify",
          target: "admin@mosaicstack.dev",
          priority: "medium",
        },
      ],
      notifyOnRejection: true,
      autoReassign: false,
      maxWaitTime: 120, // 2 hours
    };
  }

  /**
   * Determine task state based on escalation rules
   */
  private determineTaskState(rules: EscalationRule[]): "blocked" | "reassigned" | "cancelled" {
    // Check for explicit state changes
    if (rules.some((r) => r.action === "cancel")) {
      return "cancelled";
    }

    if (rules.some((r) => r.action === "reassign")) {
      return "reassigned";
    }

    if (rules.some((r) => r.action === "block")) {
      return "blocked";
    }

    // Default to blocked if any escalation occurred
    return "blocked";
  }

  /**
   * Format duration between two dates
   */
  private formatDuration(start: Date, end: Date): string {
    const durationMs = end.getTime() - start.getTime();
    const hours = Math.floor(durationMs / (1000 * 60 * 60));
    const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));

    if (hours > 0) {
      return `${String(hours)}h ${String(minutes)}m`;
    }
    return `${String(minutes)}m`;
  }

  /**
   * Generate recommendations based on failure types
   */
  private generateRecommendations(failures: FailureSummary[]): string {
    const recommendations: string[] = [];

    failures.forEach((failure) => {
      switch (failure.gateName) {
        case "type-check":
          recommendations.push(
            "- Review TypeScript errors and ensure all types are properly defined"
          );
          recommendations.push(
            "- Check for missing type definitions or incorrect type annotations"
          );
          break;

        case "test-gate":
          recommendations.push(
            "- Review failing tests and update implementation to meet test expectations"
          );
          recommendations.push("- Verify test mocks and fixtures are correctly configured");
          break;

        case "lint-gate":
          recommendations.push("- Run ESLint and fix all reported issues");
          recommendations.push(
            "- Consider adding ESLint disable comments only for false positives"
          );
          break;

        case "security-scan":
          recommendations.push(
            "- **CRITICAL**: Review and fix security vulnerabilities immediately"
          );
          recommendations.push("- Do not proceed until security issues are resolved");
          break;

        case "coverage-gate":
          recommendations.push(
            "- Add additional tests to increase coverage above minimum threshold"
          );
          recommendations.push("- Focus on untested edge cases and error paths");
          break;

        default:
          recommendations.push(`- Review ${failure.gateName} failures and address root causes`);
      }
    });

    // Deduplicate recommendations
    const uniqueRecommendations = [...new Set(recommendations)];

    return uniqueRecommendations.length > 0
      ? uniqueRecommendations.join("\n")
      : "- Review error logs and agent output for additional context";
  }
}
