import { Request, Response, NextFunction, Router } from 'express';
import { ReportsService } from '../services/reports.service';
import { ReportExecutionService } from '../services/report-execution.service';
import { ReportSchedulerService } from '../services/report-scheduler.service';
import {
  CreateReportDefinitionDto,
  UpdateReportDefinitionDto,
  CreateReportScheduleDto,
  UpdateReportScheduleDto,
  CreateReportRecipientDto,
  CreateCustomReportDto,
  UpdateCustomReportDto,
  ExecuteReportDto,
  ReportDefinitionFiltersDto,
  ReportExecutionFiltersDto,
  ReportScheduleFiltersDto,
  CustomReportFiltersDto,
} from '../dto';
import { logger } from '../../../shared/utils/logger.js';

/**
 * ReportsController
 *
 * REST API for managing reports, definitions, executions, schedules, and custom reports.
 */
export class ReportsController {
  public router: Router;

  constructor(
    private readonly reportsService: ReportsService,
    private readonly executionService: ReportExecutionService,
    private readonly schedulerService: ReportSchedulerService
  ) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Definitions
    this.router.get('/definitions', this.getDefinitions.bind(this));
    this.router.get('/definitions/categories', this.getCategories.bind(this));
    this.router.get('/definitions/:id', this.getDefinitionById.bind(this));
    this.router.post('/definitions', this.createDefinition.bind(this));
    this.router.put('/definitions/:id', this.updateDefinition.bind(this));
    this.router.delete('/definitions/:id', this.deleteDefinition.bind(this));

    // Executions
    this.router.post('/execute', this.executeReport.bind(this));
    this.router.get('/executions', this.getExecutions.bind(this));
    this.router.get('/executions/:id', this.getExecutionById.bind(this));
    this.router.post('/executions/:id/cancel', this.cancelExecution.bind(this));

    // Schedules
    this.router.get('/schedules', this.getSchedules.bind(this));
    this.router.get('/schedules/:id', this.getScheduleById.bind(this));
    this.router.post('/schedules', this.createSchedule.bind(this));
    this.router.put('/schedules/:id', this.updateSchedule.bind(this));
    this.router.patch('/schedules/:id/toggle', this.toggleSchedule.bind(this));
    this.router.delete('/schedules/:id', this.deleteSchedule.bind(this));
    this.router.post('/schedules/:id/trigger', this.triggerSchedule.bind(this));
    this.router.get('/schedules/:id/history', this.getScheduleHistory.bind(this));

    // Recipients
    this.router.get('/schedules/:scheduleId/recipients', this.getRecipients.bind(this));
    this.router.post('/schedules/:scheduleId/recipients', this.addRecipient.bind(this));
    this.router.delete('/recipients/:id', this.removeRecipient.bind(this));

    // Custom Reports
    this.router.get('/custom', this.getCustomReports.bind(this));
    this.router.get('/custom/:id', this.getCustomReportById.bind(this));
    this.router.post('/custom', this.createCustomReport.bind(this));
    this.router.put('/custom/:id', this.updateCustomReport.bind(this));
    this.router.patch('/custom/:id/favorite', this.toggleFavorite.bind(this));
    this.router.delete('/custom/:id', this.deleteCustomReport.bind(this));
  }

  // ==================== DEFINITIONS ====================

  private async getDefinitions(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const { reportType, category, isPublic, isActive, search, page, limit } = req.query;

      const filters: ReportDefinitionFiltersDto = {
        reportType: reportType as any,
        category: category as string,
        isPublic: isPublic !== undefined ? isPublic === 'true' : undefined,
        isActive: isActive !== undefined ? isActive === 'true' : undefined,
        search: search as string,
        page: page ? parseInt(page as string, 10) : undefined,
        limit: limit ? parseInt(limit as string, 10) : undefined,
      };

      const { data, total } = await this.reportsService.findAllDefinitions(tenantId, filters);

      res.json({
        success: true,
        data,
        pagination: {
          page: filters.page || 1,
          limit: filters.limit || 20,
          total,
          totalPages: Math.ceil(total / (filters.limit || 20)),
        },
      });
    } catch (error) {
      next(error);
    }
  }

  private async getCategories(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const categories = await this.reportsService.getCategories(tenantId);

      res.json({
        success: true,
        data: categories,
      });
    } catch (error) {
      next(error);
    }
  }

  private async getDefinitionById(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;

      const definition = await this.reportsService.findDefinitionById(id, tenantId);

      res.json({
        success: true,
        data: definition,
      });
    } catch (error) {
      next(error);
    }
  }

  private async createDefinition(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;
      const dto: CreateReportDefinitionDto = req.body;

      const definition = await this.reportsService.createDefinition(dto, tenantId, userId);

      logger.info('Report definition created via API', { id: definition.id, userId });

      res.status(201).json({
        success: true,
        data: definition,
      });
    } catch (error) {
      next(error);
    }
  }

  private async updateDefinition(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const dto: UpdateReportDefinitionDto = req.body;

      const definition = await this.reportsService.updateDefinition(id, dto, tenantId);

      res.json({
        success: true,
        data: definition,
      });
    } catch (error) {
      next(error);
    }
  }

  private async deleteDefinition(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;

      await this.reportsService.deleteDefinition(id, tenantId);

      res.json({
        success: true,
        message: 'Report definition deleted',
      });
    } catch (error) {
      next(error);
    }
  }

  // ==================== EXECUTIONS ====================

  private async executeReport(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;
      const dto: ExecuteReportDto = req.body;

      const execution = await this.executionService.executeReport(dto, tenantId, userId);

      res.status(202).json({
        success: true,
        message: 'Report execution started',
        data: execution,
      });
    } catch (error) {
      next(error);
    }
  }

  private async getExecutions(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const { reportDefinitionId, status, executedBy, dateFrom, dateTo, page, limit } = req.query;

      const filters: ReportExecutionFiltersDto = {
        reportDefinitionId: reportDefinitionId as string,
        status: status as any,
        executedBy: executedBy as string,
        dateFrom: dateFrom as string,
        dateTo: dateTo as string,
        page: page ? parseInt(page as string, 10) : undefined,
        limit: limit ? parseInt(limit as string, 10) : undefined,
      };

      const { data, total } = await this.executionService.findAll(tenantId, filters);

      res.json({
        success: true,
        data,
        pagination: {
          page: filters.page || 1,
          limit: filters.limit || 20,
          total,
          totalPages: Math.ceil(total / (filters.limit || 20)),
        },
      });
    } catch (error) {
      next(error);
    }
  }

  private async getExecutionById(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;

      const execution = await this.executionService.findById(id, tenantId);

      res.json({
        success: true,
        data: execution,
      });
    } catch (error) {
      next(error);
    }
  }

  private async cancelExecution(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;

      const execution = await this.executionService.cancelExecution(id, tenantId);

      res.json({
        success: true,
        message: 'Execution cancelled',
        data: execution,
      });
    } catch (error) {
      next(error);
    }
  }

  // ==================== SCHEDULES ====================

  private async getSchedules(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const { reportDefinitionId, isActive, search, page, limit } = req.query;

      const filters: ReportScheduleFiltersDto = {
        reportDefinitionId: reportDefinitionId as string,
        isActive: isActive !== undefined ? isActive === 'true' : undefined,
        search: search as string,
        page: page ? parseInt(page as string, 10) : undefined,
        limit: limit ? parseInt(limit as string, 10) : undefined,
      };

      const { data, total } = await this.reportsService.findAllSchedules(tenantId, filters);

      res.json({
        success: true,
        data,
        pagination: {
          page: filters.page || 1,
          limit: filters.limit || 20,
          total,
          totalPages: Math.ceil(total / (filters.limit || 20)),
        },
      });
    } catch (error) {
      next(error);
    }
  }

  private async getScheduleById(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;

      const schedule = await this.reportsService.findScheduleById(id, tenantId);

      res.json({
        success: true,
        data: schedule,
      });
    } catch (error) {
      next(error);
    }
  }

  private async createSchedule(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;
      const dto: CreateReportScheduleDto = req.body;

      const schedule = await this.reportsService.createSchedule(dto, tenantId, userId);

      res.status(201).json({
        success: true,
        data: schedule,
      });
    } catch (error) {
      next(error);
    }
  }

  private async updateSchedule(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const dto: UpdateReportScheduleDto = req.body;

      const schedule = await this.reportsService.updateSchedule(id, dto, tenantId);

      res.json({
        success: true,
        data: schedule,
      });
    } catch (error) {
      next(error);
    }
  }

  private async toggleSchedule(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const { isActive } = req.body;

      const schedule = await this.reportsService.toggleSchedule(id, tenantId, isActive);

      res.json({
        success: true,
        message: isActive ? 'Schedule activated' : 'Schedule deactivated',
        data: schedule,
      });
    } catch (error) {
      next(error);
    }
  }

  private async deleteSchedule(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;

      await this.reportsService.deleteSchedule(id, tenantId);

      res.json({
        success: true,
        message: 'Schedule deleted',
      });
    } catch (error) {
      next(error);
    }
  }

  private async triggerSchedule(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;

      const scheduleExecution = await this.schedulerService.triggerSchedule(id, tenantId);

      res.status(202).json({
        success: true,
        message: 'Schedule triggered',
        data: scheduleExecution,
      });
    } catch (error) {
      next(error);
    }
  }

  private async getScheduleHistory(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const { limit } = req.query;

      const history = await this.schedulerService.getScheduleHistory(
        id,
        limit ? parseInt(limit as string, 10) : undefined
      );

      res.json({
        success: true,
        data: history,
      });
    } catch (error) {
      next(error);
    }
  }

  // ==================== RECIPIENTS ====================

  private async getRecipients(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { scheduleId } = req.params;

      const recipients = await this.reportsService.findRecipientsBySchedule(scheduleId);

      res.json({
        success: true,
        data: recipients,
      });
    } catch (error) {
      next(error);
    }
  }

  private async addRecipient(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { scheduleId } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const dto: CreateReportRecipientDto = { ...req.body, scheduleId };

      const recipient = await this.reportsService.addRecipient(dto, tenantId);

      res.status(201).json({
        success: true,
        data: recipient,
      });
    } catch (error) {
      next(error);
    }
  }

  private async removeRecipient(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;

      await this.reportsService.removeRecipient(id);

      res.json({
        success: true,
        message: 'Recipient removed',
      });
    } catch (error) {
      next(error);
    }
  }

  // ==================== CUSTOM REPORTS ====================

  private async getCustomReports(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;
      const { baseDefinitionId, isFavorite, search, page, limit } = req.query;

      const filters: CustomReportFiltersDto = {
        baseDefinitionId: baseDefinitionId as string,
        isFavorite: isFavorite !== undefined ? isFavorite === 'true' : undefined,
        search: search as string,
        page: page ? parseInt(page as string, 10) : undefined,
        limit: limit ? parseInt(limit as string, 10) : undefined,
      };

      const { data, total } = await this.reportsService.findAllCustomReports(tenantId, userId, filters);

      res.json({
        success: true,
        data,
        pagination: {
          page: filters.page || 1,
          limit: filters.limit || 20,
          total,
          totalPages: Math.ceil(total / (filters.limit || 20)),
        },
      });
    } catch (error) {
      next(error);
    }
  }

  private async getCustomReportById(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      const customReport = await this.reportsService.findCustomReportById(id, tenantId, userId);

      res.json({
        success: true,
        data: customReport,
      });
    } catch (error) {
      next(error);
    }
  }

  private async createCustomReport(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;
      const dto: CreateCustomReportDto = req.body;

      const customReport = await this.reportsService.createCustomReport(dto, tenantId, userId);

      res.status(201).json({
        success: true,
        data: customReport,
      });
    } catch (error) {
      next(error);
    }
  }

  private async updateCustomReport(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;
      const dto: UpdateCustomReportDto = req.body;

      const customReport = await this.reportsService.updateCustomReport(id, dto, tenantId, userId);

      res.json({
        success: true,
        data: customReport,
      });
    } catch (error) {
      next(error);
    }
  }

  private async toggleFavorite(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      const customReport = await this.reportsService.toggleFavorite(id, tenantId, userId);

      res.json({
        success: true,
        data: customReport,
      });
    } catch (error) {
      next(error);
    }
  }

  private async deleteCustomReport(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      await this.reportsService.deleteCustomReport(id, tenantId, userId);

      res.json({
        success: true,
        message: 'Custom report deleted',
      });
    } catch (error) {
      next(error);
    }
  }
}
