import "reflect-metadata";
import { container } from "tsyringe";
import { configureDIContainer, resetDIContainer } from "../../../apps/companion/main/di-config";
import { DI_TOKENS } from "../../../apps/companion/main/di-tokens";
import { OverlaySyncService } from "@gridpilot/automation/application/services/OverlaySyncService";
import { LoggerPort } from "@gridpilot/automation/application/ports/LoggerPort";
import { IAutomationLifecycleEmitter, LifecycleCallback } from "@gridpilot/automation/infrastructure//IAutomationLifecycleEmitter";
import { AutomationEventPublisherPort, AutomationEvent } from "@gridpilot/automation/application/ports/AutomationEventPublisherPort";
import { ConsoleLogAdapter } from "@gridpilot/automation/infrastructure//logging/ConsoleLogAdapter";
import { describe, it, expect, beforeEach, afterEach, vi, SpyInstance } from 'vitest';

describe("OverlaySyncService Integration with ConsoleLogAdapter", () => {
  let consoleErrorSpy: SpyInstance<[message?: any, ...optionalParams: any[]], void>;
  let consoleWarnSpy: SpyInstance<[message?: any, ...optionalParams: any[]], void>;
  let originalNodeEnv: string | undefined;

  beforeEach(() => {
    originalNodeEnv = process.env.NODE_ENV;
    process.env.NODE_ENV = 'development';

    resetDIContainer();
    configureDIContainer();

    consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
    consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
  });

  afterEach(() => {
    consoleErrorSpy.mockRestore();
    consoleWarnSpy.mockRestore();
    if (originalNodeEnv !== undefined) {
      process.env.NODE_ENV = originalNodeEnv;
    }
    resetDIContainer();
  });

  it("should use ConsoleLogAdapter and log messages when OverlaySyncService encounters an error", async () => {
    const logger = container.resolve<LoggerPort>(DI_TOKENS.Logger);
    const overlaySyncService = container.resolve<OverlaySyncService>(DI_TOKENS.OverlaySyncPort);

    expect(logger).toBeInstanceOf(ConsoleLogAdapter);

    const mockLifecycleEmitter: IAutomationLifecycleEmitter = {
      onLifecycle: vi.fn((_cb: LifecycleCallback) => {
        throw new Error("Test lifecycle emitter error");
      }),
      offLifecycle: vi.fn(),
    };

    const mockPublisher: AutomationEventPublisherPort = {
      publish: vi.fn(),
    };

    const serviceWithMockedEmitter = new OverlaySyncService({
      lifecycleEmitter: mockLifecycleEmitter,
      publisher: mockPublisher,
      logger: logger,
    });

    const action = { id: "test-action-1", label: "Test Action" };
    await serviceWithMockedEmitter.execute(action);

    expect(consoleErrorSpy).toHaveBeenCalledWith(
      expect.stringContaining("OverlaySyncService: failed to subscribe to lifecycleEmitter"),
      expect.any(Error),
      expect.objectContaining({ actionId: action.id }),
    );
    expect(consoleWarnSpy).not.toHaveBeenCalled();
  });

  it("should use ConsoleLogAdapter and log warn messages when OverlaySyncService fails to publish", async () => {
    const logger = container.resolve<LoggerPort>(DI_TOKENS.Logger);
    expect(logger).toBeInstanceOf(ConsoleLogAdapter);

    const mockLifecycleEmitter: IAutomationLifecycleEmitter = {
      onLifecycle: vi.fn(),
      offLifecycle: vi.fn(),
    };

    const mockPublisher: AutomationEventPublisherPort = {
      publish: vi.fn((_event: AutomationEvent) => {
        throw new Error("Test publish error");
      }),
    };

    const serviceWithMockedPublisher = new OverlaySyncService({
      lifecycleEmitter: mockLifecycleEmitter,
      publisher: mockPublisher,
      logger: logger,
    });

    const action = { id: "test-action-2", label: "Test Action" };
    await serviceWithMockedPublisher.execute(action);

    expect(consoleWarnSpy).toHaveBeenCalledWith(
      expect.stringContaining("OverlaySyncService: publisher.publish failed"),
      expect.objectContaining({
        actionId: action.id,
        error: expect.any(Error),
      }),
    );
    expect(consoleErrorSpy).not.toHaveBeenCalled();
  });
});
