import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
import type { BaseAppConfig } from '@stock-bot/config';
import { ServiceApplication } from '../src/service-application';
import type { ServiceApplicationConfig, ServiceLifecycleHooks } from '../src/service-application';

// Mock logger module
const mockLogger = {
  info: mock(() => {}),
  error: mock(() => {}),
  warn: mock(() => {}),
  debug: mock(() => {}),
  child: mock(() => mockLogger),
};

mock.module('@stock-bot/logger', () => ({
  getLogger: () => mockLogger,
  setLoggerConfig: mock(() => {}),
  shutdownLoggers: mock(() => Promise.resolve()),
}));

// Mock shutdown module
const mockShutdownInstance = {
  onShutdown: mock(() => {}),
  onShutdownHigh: mock(() => {}),
  onShutdownMedium: mock(() => {}),
  onShutdownLow: mock(() => {}),
  register: mock(() => {}),
  registerAsync: mock(() => {}),
  handleTermination: mock(() => {}),
  executeCallbacks: mock(() => Promise.resolve()),
};

const mockShutdown = {
  getInstance: mock(() => mockShutdownInstance)
};

mock.module('@stock-bot/shutdown', () => ({
  Shutdown: mockShutdown,
}));

// Mock Bun.serve
const mockServer = {
  stop: mock(() => {}),
  port: 3000,
  hostname: '0.0.0.0',
};

const originalBunServe = Bun.serve;
Bun.serve = mock(() => mockServer);

const mockConfig: BaseAppConfig = {
  name: 'test-service',
  version: '1.0.0',
  environment: 'test',
  service: {
    name: 'test-service',
    serviceName: 'test-service',
    port: 3000,
    host: '0.0.0.0',
    healthCheckPath: '/health',
    metricsPath: '/metrics',
    shutdownTimeout: 5000,
    cors: {
      enabled: true,
      origin: '*',
      credentials: true,
    },
  },
  log: {
    level: 'info',
    format: 'json',
    pretty: false,
  },
};

describe('ServiceApplication', () => {
  let app: ServiceApplication;

  afterEach(() => {
    // Reset mocks
    mockLogger.info.mockReset();
    mockLogger.error.mockReset();
    mockLogger.warn.mockReset();
    mockLogger.debug.mockReset();
    mockShutdownInstance.onShutdown.mockReset();
    mockShutdownInstance.onShutdownHigh.mockReset();
    mockShutdownInstance.onShutdownMedium.mockReset();
    mockShutdownInstance.onShutdownLow.mockReset();
    mockShutdownInstance.register.mockReset();
    mockShutdownInstance.registerAsync.mockReset();
    mockShutdownInstance.handleTermination.mockReset();
    mockShutdownInstance.executeCallbacks.mockReset();

    // Clean up app if it exists
    if (app) {
      app.stop().catch(() => {});
      app = null as any;
    }
  });

  describe('constructor', () => {
    it('should create service application', () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
      };

      app = new ServiceApplication(mockConfig, serviceConfig);
      expect(app).toBeDefined();
    });

    it('should create with full config', () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        addInfoEndpoint: true,
        enableHandlers: true,
        enableScheduledJobs: true,
        shutdownTimeout: 10000,
        corsConfig: {
          origin: 'https://example.com',
          credentials: true,
        },
        serviceMetadata: {
          version: '1.0.0',
          description: 'Test service',
        },
      };

      app = new ServiceApplication(mockConfig, serviceConfig);
      expect(app).toBeDefined();
    });

    it('should initialize shutdown with custom timeout', () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        shutdownTimeout: 30000,
      };

      app = new ServiceApplication(mockConfig, serviceConfig);
      expect(mockShutdown.getInstance).toHaveBeenCalledWith({
        timeout: 30000,
      });
    });
  });

  describe('lifecycle', () => {
    it('should support lifecycle hooks', () => {
      const hooks: ServiceLifecycleHooks = {
        beforeInitialize: mock(() => Promise.resolve()),
        afterInitialize: mock(() => Promise.resolve()),
        beforeSetupRoutes: mock(() => {}),
        afterSetupRoutes: mock(() => {}),
        onStart: mock(() => Promise.resolve()),
        onStop: mock(() => Promise.resolve()),
      };

      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
      };

      app = new ServiceApplication(mockConfig, serviceConfig, hooks);
      expect(app).toBeDefined();
    });
  });

  describe('getters', () => {
    it('should have public methods', () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
      };

      app = new ServiceApplication(mockConfig, serviceConfig);
      expect(app.start).toBeDefined();
      expect(app.stop).toBeDefined();
      expect(app.getServiceContainer).toBeDefined();
      expect(app.getApp).toBeDefined();
    });
  });

  describe('error scenarios', () => {
    it('should handle missing service name', () => {
      const configWithoutServiceName = {
        ...mockConfig,
        service: {
          ...mockConfig.service,
          serviceName: undefined,
        },
      };

      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'fallback-service',
      };

      // Should not throw - uses fallback
      app = new ServiceApplication(configWithoutServiceName as any, serviceConfig);
      expect(app).toBeDefined();
    });
  });

  describe('start method', () => {
    const mockContainer = {
      resolve: mock((name: string) => {
        if (name === 'serviceContainer') {
          return { test: 'container' };
        }
        if (name === 'handlerRegistry') {
          return {
            getAllHandlersWithSchedule: () => new Map(),
            getHandlerNames: () => [],
            getHandlerService: () => 'test-service',
            getOperation: () => ({}),
          };
        }
        if (name === 'queueManager') {
          return {
            getQueue: () => ({
              addScheduledJob: mock(() => Promise.resolve()),
            }),
            startAllWorkers: mock(() => {}),
            shutdown: mock(() => Promise.resolve()),
          };
        }
        return null;
      }),
    };

    const mockContainerFactory = mock(async () => mockContainer);
    const mockRouteFactory = mock(() => {
      const { Hono } = require('hono');
      const routes = new Hono();
      // Add a simple test route
      routes.get('/test', c => c.json({ test: true }));
      return routes;
    });
    const mockHandlerInitializer = mock(() => Promise.resolve());

    it('should start service with basic configuration', async () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        addInfoEndpoint: false,
      };

      app = new ServiceApplication(mockConfig, serviceConfig);

      await app.start(mockContainerFactory, mockRouteFactory);

      expect(mockContainerFactory).toHaveBeenCalledWith(
        expect.objectContaining({
          service: expect.objectContaining({ serviceName: 'test-service' }),
        })
      );
      expect(mockRouteFactory).toHaveBeenCalledWith({ test: 'container' });
      expect(mockLogger.info).toHaveBeenCalledWith('test-service service started on port 3000');
    });

    it('should initialize handlers when enabled', async () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        enableHandlers: true,
      };

      app = new ServiceApplication(mockConfig, serviceConfig);

      await app.start(mockContainerFactory, mockRouteFactory, mockHandlerInitializer);

      expect(mockHandlerInitializer).toHaveBeenCalledWith(
        expect.objectContaining({
          test: 'container',
          _diContainer: mockContainer,
        })
      );
      expect(mockLogger.info).toHaveBeenCalledWith('Handlers initialized');
    });

    it('should call lifecycle hooks', async () => {
      const hooks: ServiceLifecycleHooks = {
        onContainerReady: mock(() => {}),
        onAppReady: mock(() => {}),
        onBeforeStart: mock(() => {}),
        onStarted: mock(() => {}),
      };

      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
      };

      app = new ServiceApplication(mockConfig, serviceConfig, hooks);

      await app.start(mockContainerFactory, mockRouteFactory);

      expect(hooks.onContainerReady).toHaveBeenCalledWith({ test: 'container' });
      expect(hooks.onAppReady).toHaveBeenCalled();
      expect(hooks.onBeforeStart).toHaveBeenCalled();
      expect(hooks.onStarted).toHaveBeenCalledWith(3000);
    });

    it('should handle start errors', async () => {
      const errorFactory = mock(() => {
        throw new Error('Container creation failed');
      });

      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
      };

      app = new ServiceApplication(mockConfig, serviceConfig);

      await expect(app.start(errorFactory, mockRouteFactory)).rejects.toThrow(
        'Container creation failed'
      );
      expect(mockLogger.error).toHaveBeenCalledWith('DETAILED ERROR:', expect.any(Error));
    });

    it('should initialize scheduled jobs when enabled', async () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        enableScheduledJobs: true,
      };

      const mockHandlerRegistry = {
        getAllHandlersWithSchedule: () =>
          new Map([
            [
              'testHandler',
              {
                scheduledJobs: [
                  {
                    operation: 'processData',
                    cronPattern: '0 * * * *',
                    priority: 5,
                    immediately: false,
                    payload: { test: true },
                  },
                ],
              },
            ],
          ]),
        getHandlerService: () => 'test-service',
        getHandlerNames: () => ['testHandler'],
        getOperation: () => ({ name: 'processData' }),
      };

      const mockQueue = {
        addScheduledJob: mock(() => Promise.resolve()),
      };

      const mockQueueManager = {
        getQueue: mock(() => mockQueue),
        startAllWorkers: mock(() => {}),
        shutdown: mock(() => Promise.resolve()),
      };

      const containerWithJobs = {
        resolve: mock((name: string) => {
          if (name === 'serviceContainer') {
            return { test: 'container' };
          }
          if (name === 'handlerRegistry') {
            return mockHandlerRegistry;
          }
          if (name === 'queueManager') {
            return mockQueueManager;
          }
          return null;
        }),
      };

      const jobContainerFactory = mock(async () => containerWithJobs);

      app = new ServiceApplication(mockConfig, serviceConfig);

      await app.start(jobContainerFactory, mockRouteFactory);

      expect(mockQueueManager.getQueue).toHaveBeenCalledWith('testHandler', {
        handlerRegistry: mockHandlerRegistry,
      });
      expect(mockQueue.addScheduledJob).toHaveBeenCalledWith(
        'processData',
        { handler: 'testHandler', operation: 'processData', payload: { test: true } },
        '0 * * * *',
        expect.objectContaining({ priority: 5, repeat: { immediately: false } })
      );
      expect(mockQueueManager.startAllWorkers).toHaveBeenCalled();
      expect(mockLogger.info).toHaveBeenCalledWith('Scheduled jobs created', { totalJobs: 1 });
    });
  });

  describe('stop method', () => {
    it('should trigger shutdown', async () => {
      const mockShutdownInstance = {
        shutdown: mock(() => Promise.resolve()),
        onShutdown: mock(() => {}),
        onShutdownHigh: mock(() => {}),
        onShutdownMedium: mock(() => {}),
        onShutdownLow: mock(() => {}),
      };

      mock.module('@stock-bot/shutdown', () => ({
        Shutdown: {
          getInstance: () => mockShutdownInstance,
        },
      }));

      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
      };

      app = new ServiceApplication(mockConfig, serviceConfig);

      await app.stop();

      expect(mockShutdownInstance.shutdown).toHaveBeenCalled();
      expect(mockLogger.info).toHaveBeenCalledWith('Stopping test-service service...');
    });
  });

  describe('getters', () => {
    it('should return service container after start', async () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
      };

      app = new ServiceApplication(mockConfig, serviceConfig);

      // Before start
      expect(app.getServiceContainer()).toBeNull();
      expect(app.getApp()).toBeNull();

      // After start
      const mockContainer = {
        resolve: mock(() => ({ test: 'container' })),
      };
      await app.start(
        async () => mockContainer,
        () => {
          const { Hono } = require('hono');
          const routes = new Hono();
          routes.get('/test', c => c.json({ ok: true }));
          return routes;
        }
      );

      expect(app.getServiceContainer()).toEqual({ test: 'container' });
      expect(app.getApp()).toBeDefined();
    });
  });

  describe('shutdown handlers', () => {
    it('should register all shutdown handlers during start', async () => {
      const mockShutdownInstance = {
        shutdown: mock(() => Promise.resolve()),
        onShutdown: mock(() => {}),
        onShutdownHigh: mock(() => {}),
        onShutdownMedium: mock(() => {}),
        onShutdownLow: mock(() => {}),
      };

      mock.module('@stock-bot/shutdown', () => ({
        Shutdown: {
          getInstance: () => mockShutdownInstance,
        },
      }));

      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        enableScheduledJobs: true,
      };

      const hooks: ServiceLifecycleHooks = {
        onBeforeShutdown: mock(() => {}),
      };

      app = new ServiceApplication(mockConfig, serviceConfig, hooks);

      const mockContainer = {
        resolve: mock((name: string) => {
          if (name === 'serviceContainer') {
            return { test: 'container' };
          }
          if (name === 'handlerRegistry') {
            return {
              getAllHandlersWithSchedule: () => new Map(),
              getHandlerNames: () => [],
            };
          }
          if (name === 'queueManager') {
            return {
              shutdown: mock(() => Promise.resolve()),
              startAllWorkers: mock(() => {}),
            };
          }
          if (name === 'mongoClient') {
            return { disconnect: mock(() => Promise.resolve()) };
          }
          if (name === 'postgresClient') {
            return { disconnect: mock(() => Promise.resolve()) };
          }
          if (name === 'questdbClient') {
            return { disconnect: mock(() => Promise.resolve()) };
          }
          return null;
        }),
      };

      await app.start(
        async () => mockContainer,
        () => {
          const { Hono } = require('hono');
          const routes = new Hono();
          routes.get('/test', c => c.json({ ok: true }));
          return routes;
        }
      );

      // Should have registered shutdown handlers
      expect(mockShutdownInstance.onShutdown).toHaveBeenCalledTimes(5); // Queue, HTTP, Custom, Services, Loggers

      // Test the handlers by calling them
      const shutdownHandlers = (mockShutdownInstance.onShutdown as any).mock.calls;

      // Find and execute queue shutdown handler (priority 9)
      const queueHandler = shutdownHandlers.find(call => call[2] === 'Queue System');
      if (queueHandler) {
        await queueHandler[0]();
        expect(mockContainer.resolve).toHaveBeenCalledWith('queueManager');
      }

      // Find and execute services shutdown handler (priority 5)
      const servicesHandler = shutdownHandlers.find(call => call[2] === 'Services');
      await servicesHandler[0]();
      expect(mockContainer.resolve).toHaveBeenCalledWith('mongoClient');
      expect(mockContainer.resolve).toHaveBeenCalledWith('postgresClient');
      expect(mockContainer.resolve).toHaveBeenCalledWith('questdbClient');

      // Logger shutdown handler is also registered
    });
  });

  describe('info endpoint', () => {
    it('should add info endpoint when enabled', async () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        addInfoEndpoint: true,
        serviceMetadata: {
          version: '2.0.0',
          description: 'Test service description',
          endpoints: {
            '/api/v1': 'Main API',
            '/health': 'Health check',
          },
        },
      };

      app = new ServiceApplication(mockConfig, serviceConfig);

      const mockContainer = {
        resolve: mock(() => ({ test: 'container' })),
      };

      await app.start(
        async () => mockContainer,
        () => {
          const { Hono } = require('hono');
          const routes = new Hono();
          routes.get('/test', c => c.json({ ok: true }));
          return routes;
        }
      );

      const honoApp = app.getApp();
      expect(honoApp).toBeDefined();

      // Test the info endpoint
      const response = await honoApp!.request('/');
      const json = await response.json();

      expect(json).toEqual({
        name: 'test-service',
        version: '2.0.0',
        description: 'Test service description',
        status: 'running',
        timestamp: expect.any(String),
        endpoints: {
          '/api/v1': 'Main API',
          '/health': 'Health check',
        },
      });
    });

    it('should not add info endpoint when disabled', async () => {
      const serviceConfig: ServiceApplicationConfig = {
        serviceName: 'test-service',
        addInfoEndpoint: false,
      };

      app = new ServiceApplication(mockConfig, serviceConfig);

      const mockContainer = {
        resolve: mock(() => ({ test: 'container' })),
      };

      await app.start(
        async () => mockContainer,
        () => {
          const { Hono } = require('hono');
          const routes = new Hono();
          routes.get('/test', c => c.json({ ok: true }));
          return routes;
        }
      );

      const honoApp = app.getApp();
      const response = await honoApp!.request('/');
      expect(response.status).toBe(404);
    });
  });
});
