import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { ConfigService } from "@nestjs/config";
import { RunnerJobStatus } from "@prisma/client";
import { CoordinatorIntegrationController } from "./coordinator-integration.controller";
import { CoordinatorIntegrationService } from "./coordinator-integration.service";
import type { CoordinatorJobResult, CoordinatorHealthStatus } from "./interfaces";
import { CoordinatorJobStatus } from "./dto";
import { ApiKeyGuard } from "../common/guards";

describe("CoordinatorIntegrationController", () => {
  let controller: CoordinatorIntegrationController;

  const mockJobResult: CoordinatorJobResult = {
    jobId: "job-123",
    status: "PENDING",
    queueName: "mosaic:main",
  };

  const mockJob = {
    id: "job-123",
    workspaceId: "workspace-123",
    type: "code-task",
    status: RunnerJobStatus.PENDING,
    priority: 10,
    progressPercent: 0,
    agentTaskId: null,
    result: null,
    error: null,
    startedAt: null,
    completedAt: null,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  const mockHealthStatus: CoordinatorHealthStatus = {
    api: true,
    bullmq: {
      connected: true,
      queues: { main: 5, runner: 2 },
    },
    timestamp: new Date(),
  };

  const mockService = {
    createJob: vi.fn(),
    updateJobStatus: vi.fn(),
    updateJobProgress: vi.fn(),
    completeJob: vi.fn(),
    failJob: vi.fn(),
    getJobDetails: vi.fn(),
    getIntegrationHealth: vi.fn(),
  };

  const mockConfigService = {
    get: vi.fn().mockReturnValue("test-api-key-12345"),
  };

  beforeEach(async () => {
    vi.clearAllMocks();

    const module: TestingModule = await Test.createTestingModule({
      controllers: [CoordinatorIntegrationController],
      providers: [
        { provide: CoordinatorIntegrationService, useValue: mockService },
        { provide: ConfigService, useValue: mockConfigService },
      ],
    })
      .overrideGuard(ApiKeyGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<CoordinatorIntegrationController>(CoordinatorIntegrationController);
  });

  describe("POST /coordinator/jobs", () => {
    it("should create a job and return job result", async () => {
      const dto = {
        workspaceId: "workspace-123",
        type: "code-task",
        issueNumber: 42,
        repository: "mosaic/stack",
      };

      mockService.createJob.mockResolvedValue(mockJobResult);

      const result = await controller.createJob(dto);

      expect(result).toEqual(mockJobResult);
      expect(mockService.createJob).toHaveBeenCalledWith(dto);
    });
  });

  describe("PATCH /coordinator/jobs/:id/status", () => {
    it("should update job status", async () => {
      const updatedJob = { ...mockJob, status: RunnerJobStatus.RUNNING };
      mockService.updateJobStatus.mockResolvedValue(updatedJob);

      const result = await controller.updateJobStatus("job-123", {
        status: CoordinatorJobStatus.RUNNING,
        agentId: "agent-42",
      });

      expect(result.status).toBe(RunnerJobStatus.RUNNING);
      expect(mockService.updateJobStatus).toHaveBeenCalledWith("job-123", {
        status: CoordinatorJobStatus.RUNNING,
        agentId: "agent-42",
      });
    });
  });

  describe("PATCH /coordinator/jobs/:id/progress", () => {
    it("should update job progress", async () => {
      const updatedJob = { ...mockJob, progressPercent: 50 };
      mockService.updateJobProgress.mockResolvedValue(updatedJob);

      const result = await controller.updateJobProgress("job-123", {
        progressPercent: 50,
        currentStep: "Running tests",
      });

      expect(result.progressPercent).toBe(50);
      expect(mockService.updateJobProgress).toHaveBeenCalledWith("job-123", {
        progressPercent: 50,
        currentStep: "Running tests",
      });
    });
  });

  describe("POST /coordinator/jobs/:id/complete", () => {
    it("should complete a job", async () => {
      const completedJob = {
        ...mockJob,
        status: RunnerJobStatus.COMPLETED,
        progressPercent: 100,
      };
      mockService.completeJob.mockResolvedValue(completedJob);

      const result = await controller.completeJob("job-123", {
        result: { commitSha: "abc123" },
      });

      expect(result.status).toBe(RunnerJobStatus.COMPLETED);
      expect(mockService.completeJob).toHaveBeenCalledWith("job-123", {
        result: { commitSha: "abc123" },
      });
    });
  });

  describe("POST /coordinator/jobs/:id/fail", () => {
    it("should fail a job", async () => {
      const failedJob = {
        ...mockJob,
        status: RunnerJobStatus.FAILED,
        error: "Test failed",
      };
      mockService.failJob.mockResolvedValue(failedJob);

      const result = await controller.failJob("job-123", {
        error: "Test failed",
        gateResults: { lint: true, test: false },
      });

      expect(result.status).toBe(RunnerJobStatus.FAILED);
      expect(result.error).toBe("Test failed");
      expect(mockService.failJob).toHaveBeenCalledWith("job-123", {
        error: "Test failed",
        gateResults: { lint: true, test: false },
      });
    });
  });

  describe("GET /coordinator/jobs/:id", () => {
    it("should return job details", async () => {
      const jobWithDetails = { ...mockJob, steps: [], events: [] };
      mockService.getJobDetails.mockResolvedValue(jobWithDetails);

      const result = await controller.getJobDetails("job-123");

      expect(result).toEqual(jobWithDetails);
      expect(mockService.getJobDetails).toHaveBeenCalledWith("job-123");
    });
  });

  describe("GET /coordinator/health", () => {
    it("should return integration health status", async () => {
      mockService.getIntegrationHealth.mockResolvedValue(mockHealthStatus);

      const result = await controller.getHealth();

      expect(result.api).toBe(true);
      expect(result.bullmq.connected).toBe(true);
      expect(mockService.getIntegrationHealth).toHaveBeenCalled();
    });
  });
});
