import { query, queryOne, getClient } from '../../config/database.js';
import { NotFoundError, ValidationError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

// ============================================================================
// TYPES
// ============================================================================

export type ReportType = 'financial' | 'accounting' | 'tax' | 'management' | 'custom';
export type ExecutionStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
export type DeliveryMethod = 'none' | 'email' | 'storage' | 'webhook';

export interface ReportDefinition {
  id: string;
  tenant_id: string;
  code: string;
  name: string;
  description: string | null;
  report_type: ReportType;
  category: string | null;
  base_query: string | null;
  query_function: string | null;
  parameters_schema: Record<string, any>;
  columns_config: any[];
  grouping_options: string[];
  totals_config: Record<string, any>;
  export_formats: string[];
  pdf_template: string | null;
  xlsx_template: string | null;
  is_system: boolean;
  is_active: boolean;
  required_permissions: string[];
  version: number;
  created_at: Date;
}

export interface ReportExecution {
  id: string;
  tenant_id: string;
  definition_id: string;
  definition_name?: string;
  definition_code?: string;
  parameters: Record<string, any>;
  status: ExecutionStatus;
  started_at: Date | null;
  completed_at: Date | null;
  execution_time_ms: number | null;
  row_count: number | null;
  result_data: any;
  result_summary: Record<string, any> | null;
  output_files: any[];
  error_message: string | null;
  error_details: Record<string, any> | null;
  requested_by: string;
  requested_by_name?: string;
  created_at: Date;
}

export interface ReportSchedule {
  id: string;
  tenant_id: string;
  definition_id: string;
  definition_name?: string;
  company_id: string | null;
  name: string;
  default_parameters: Record<string, any>;
  cron_expression: string;
  timezone: string;
  is_active: boolean;
  last_execution_id: string | null;
  last_run_at: Date | null;
  next_run_at: Date | null;
  delivery_method: DeliveryMethod;
  delivery_config: Record<string, any>;
  created_at: Date;
}

export interface CreateReportDefinitionDto {
  code: string;
  name: string;
  description?: string;
  report_type?: ReportType;
  category?: string;
  base_query?: string;
  query_function?: string;
  parameters_schema?: Record<string, any>;
  columns_config?: any[];
  export_formats?: string[];
  required_permissions?: string[];
}

export interface ExecuteReportDto {
  definition_id: string;
  parameters: Record<string, any>;
}

export interface ReportFilters {
  report_type?: ReportType;
  category?: string;
  is_system?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

// ============================================================================
// SERVICE
// ============================================================================

class ReportsService {
  // ==================== DEFINITIONS ====================

  async findAllDefinitions(
    tenantId: string,
    filters: ReportFilters = {}
  ): Promise<{ data: ReportDefinition[]; total: number }> {
    const { report_type, category, is_system, search, page = 1, limit = 20 } = filters;
    const conditions: string[] = ['tenant_id = $1', 'is_active = true'];
    const params: any[] = [tenantId];
    let idx = 2;

    if (report_type) {
      conditions.push(`report_type = $${idx++}`);
      params.push(report_type);
    }

    if (category) {
      conditions.push(`category = $${idx++}`);
      params.push(category);
    }

    if (is_system !== undefined) {
      conditions.push(`is_system = $${idx++}`);
      params.push(is_system);
    }

    if (search) {
      conditions.push(`(name ILIKE $${idx} OR code ILIKE $${idx} OR description ILIKE $${idx})`);
      params.push(`%${search}%`);
      idx++;
    }

    const whereClause = conditions.join(' AND ');

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM reports.report_definitions WHERE ${whereClause}`,
      params
    );

    const offset = (page - 1) * limit;
    params.push(limit, offset);

    const data = await query<ReportDefinition>(
      `SELECT * FROM reports.report_definitions
       WHERE ${whereClause}
       ORDER BY is_system DESC, name ASC
       LIMIT $${idx} OFFSET $${idx + 1}`,
      params
    );

    return {
      data,
      total: parseInt(countResult?.count || '0', 10),
    };
  }

  async findDefinitionById(id: string, tenantId: string): Promise<ReportDefinition> {
    const definition = await queryOne<ReportDefinition>(
      `SELECT * FROM reports.report_definitions WHERE id = $1 AND tenant_id = $2`,
      [id, tenantId]
    );

    if (!definition) {
      throw new NotFoundError('Definición de reporte no encontrada');
    }

    return definition;
  }

  async findDefinitionByCode(code: string, tenantId: string): Promise<ReportDefinition | null> {
    return queryOne<ReportDefinition>(
      `SELECT * FROM reports.report_definitions WHERE code = $1 AND tenant_id = $2`,
      [code, tenantId]
    );
  }

  async createDefinition(
    dto: CreateReportDefinitionDto,
    tenantId: string,
    userId: string
  ): Promise<ReportDefinition> {
    const definition = await queryOne<ReportDefinition>(
      `INSERT INTO reports.report_definitions (
        tenant_id, code, name, description, report_type, category,
        base_query, query_function, parameters_schema, columns_config,
        export_formats, required_permissions, created_by
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
      RETURNING *`,
      [
        tenantId,
        dto.code,
        dto.name,
        dto.description || null,
        dto.report_type || 'custom',
        dto.category || null,
        dto.base_query || null,
        dto.query_function || null,
        JSON.stringify(dto.parameters_schema || {}),
        JSON.stringify(dto.columns_config || []),
        JSON.stringify(dto.export_formats || ['pdf', 'xlsx', 'csv']),
        JSON.stringify(dto.required_permissions || []),
        userId,
      ]
    );

    logger.info('Report definition created', { definitionId: definition?.id, code: dto.code });

    return definition!;
  }

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

  async executeReport(
    dto: ExecuteReportDto,
    tenantId: string,
    userId: string
  ): Promise<ReportExecution> {
    const definition = await this.findDefinitionById(dto.definition_id, tenantId);

    // Validar parámetros contra el schema
    this.validateParameters(dto.parameters, definition.parameters_schema);

    // Crear registro de ejecución
    const execution = await queryOne<ReportExecution>(
      `INSERT INTO reports.report_executions (
        tenant_id, definition_id, parameters, status, requested_by
      ) VALUES ($1, $2, $3, 'pending', $4)
      RETURNING *`,
      [tenantId, dto.definition_id, JSON.stringify(dto.parameters), userId]
    );

    // Ejecutar el reporte de forma asíncrona
    this.runReportExecution(execution!.id, definition, dto.parameters, tenantId)
      .catch(err => logger.error('Report execution failed', { executionId: execution!.id, error: err }));

    return execution!;
  }

  private async runReportExecution(
    executionId: string,
    definition: ReportDefinition,
    parameters: Record<string, any>,
    tenantId: string
  ): Promise<void> {
    const startTime = Date.now();

    try {
      // Marcar como ejecutando
      await query(
        `UPDATE reports.report_executions SET status = 'running', started_at = NOW() WHERE id = $1`,
        [executionId]
      );

      let resultData: any;
      let rowCount = 0;

      if (definition.query_function) {
        // Ejecutar función PostgreSQL
        const funcParams = this.buildFunctionParams(definition.query_function, parameters, tenantId);
        resultData = await query(
          `SELECT * FROM ${definition.query_function}(${funcParams.placeholders})`,
          funcParams.values
        );
        rowCount = resultData.length;
      } else if (definition.base_query) {
        // Ejecutar query base con parámetros sustituidos
        // IMPORTANTE: Sanitizar los parámetros para evitar SQL injection
        const sanitizedQuery = this.buildSafeQuery(definition.base_query, parameters, tenantId);
        resultData = await query(sanitizedQuery.sql, sanitizedQuery.values);
        rowCount = resultData.length;
      } else {
        throw new Error('La definición del reporte no tiene query ni función definida');
      }

      const executionTime = Date.now() - startTime;

      // Calcular resumen si hay config de totales
      const resultSummary = this.calculateSummary(resultData, definition.totals_config);

      // Actualizar con resultados
      await query(
        `UPDATE reports.report_executions
         SET status = 'completed',
             completed_at = NOW(),
             execution_time_ms = $2,
             row_count = $3,
             result_data = $4,
             result_summary = $5
         WHERE id = $1`,
        [executionId, executionTime, rowCount, JSON.stringify(resultData), JSON.stringify(resultSummary)]
      );

      logger.info('Report execution completed', { executionId, rowCount, executionTime });

    } catch (error: any) {
      const executionTime = Date.now() - startTime;

      await query(
        `UPDATE reports.report_executions
         SET status = 'failed',
             completed_at = NOW(),
             execution_time_ms = $2,
             error_message = $3,
             error_details = $4
         WHERE id = $1`,
        [
          executionId,
          executionTime,
          error.message,
          JSON.stringify({ stack: error.stack }),
        ]
      );

      logger.error('Report execution failed', { executionId, error: error.message });
    }
  }

  private buildFunctionParams(
    functionName: string,
    parameters: Record<string, any>,
    tenantId: string
  ): { placeholders: string; values: any[] } {
    // Construir parámetros para funciones conocidas
    const values: any[] = [tenantId];
    let idx = 2;

    if (functionName.includes('trial_balance')) {
      values.push(
        parameters.company_id || null,
        parameters.date_from,
        parameters.date_to,
        parameters.include_zero || false
      );
      return { placeholders: '$1, $2, $3, $4, $5', values };
    }

    if (functionName.includes('general_ledger')) {
      values.push(
        parameters.company_id || null,
        parameters.account_id,
        parameters.date_from,
        parameters.date_to
      );
      return { placeholders: '$1, $2, $3, $4, $5', values };
    }

    // Default: solo tenant_id
    return { placeholders: '$1', values };
  }

  private buildSafeQuery(
    baseQuery: string,
    parameters: Record<string, any>,
    tenantId: string
  ): { sql: string; values: any[] } {
    // Reemplazar placeholders de forma segura
    let sql = baseQuery;
    const values: any[] = [tenantId];
    let idx = 2;

    // Reemplazar {{tenant_id}} con $1
    sql = sql.replace(/\{\{tenant_id\}\}/g, '$1');

    // Reemplazar otros parámetros
    for (const [key, value] of Object.entries(parameters)) {
      const placeholder = `{{${key}}}`;
      if (sql.includes(placeholder)) {
        sql = sql.replace(new RegExp(placeholder, 'g'), `$${idx}`);
        values.push(value);
        idx++;
      }
    }

    return { sql, values };
  }

  private calculateSummary(data: any[], totalsConfig: Record<string, any>): Record<string, any> {
    if (!totalsConfig.show_totals || !totalsConfig.total_columns) {
      return {};
    }

    const summary: Record<string, number> = {};

    for (const column of totalsConfig.total_columns) {
      summary[column] = data.reduce((sum, row) => sum + (parseFloat(row[column]) || 0), 0);
    }

    return summary;
  }

  private validateParameters(params: Record<string, any>, schema: Record<string, any>): void {
    for (const [key, config] of Object.entries(schema)) {
      const paramConfig = config as { required?: boolean; type?: string };

      if (paramConfig.required && (params[key] === undefined || params[key] === null)) {
        throw new ValidationError(`Parámetro requerido: ${key}`);
      }
    }
  }

  async findExecutionById(id: string, tenantId: string): Promise<ReportExecution> {
    const execution = await queryOne<ReportExecution>(
      `SELECT re.*,
              rd.name as definition_name,
              rd.code as definition_code,
              u.full_name as requested_by_name
       FROM reports.report_executions re
       JOIN reports.report_definitions rd ON re.definition_id = rd.id
       JOIN auth.users u ON re.requested_by = u.id
       WHERE re.id = $1 AND re.tenant_id = $2`,
      [id, tenantId]
    );

    if (!execution) {
      throw new NotFoundError('Ejecución de reporte no encontrada');
    }

    return execution;
  }

  async findRecentExecutions(
    tenantId: string,
    definitionId?: string,
    limit: number = 20
  ): Promise<ReportExecution[]> {
    let sql = `
      SELECT re.*,
             rd.name as definition_name,
             rd.code as definition_code,
             u.full_name as requested_by_name
      FROM reports.report_executions re
      JOIN reports.report_definitions rd ON re.definition_id = rd.id
      JOIN auth.users u ON re.requested_by = u.id
      WHERE re.tenant_id = $1
    `;
    const params: any[] = [tenantId];

    if (definitionId) {
      sql += ` AND re.definition_id = $2`;
      params.push(definitionId);
    }

    sql += ` ORDER BY re.created_at DESC LIMIT $${params.length + 1}`;
    params.push(limit);

    return query<ReportExecution>(sql, params);
  }

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

  async findAllSchedules(tenantId: string): Promise<ReportSchedule[]> {
    return query<ReportSchedule>(
      `SELECT rs.*,
              rd.name as definition_name
       FROM reports.report_schedules rs
       JOIN reports.report_definitions rd ON rs.definition_id = rd.id
       WHERE rs.tenant_id = $1
       ORDER BY rs.name`,
      [tenantId]
    );
  }

  async createSchedule(
    data: {
      definition_id: string;
      name: string;
      cron_expression: string;
      default_parameters?: Record<string, any>;
      company_id?: string;
      timezone?: string;
      delivery_method?: DeliveryMethod;
      delivery_config?: Record<string, any>;
    },
    tenantId: string,
    userId: string
  ): Promise<ReportSchedule> {
    // Verificar que la definición existe
    await this.findDefinitionById(data.definition_id, tenantId);

    const schedule = await queryOne<ReportSchedule>(
      `INSERT INTO reports.report_schedules (
        tenant_id, definition_id, name, cron_expression,
        default_parameters, company_id, timezone,
        delivery_method, delivery_config, created_by
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
      RETURNING *`,
      [
        tenantId,
        data.definition_id,
        data.name,
        data.cron_expression,
        JSON.stringify(data.default_parameters || {}),
        data.company_id || null,
        data.timezone || 'America/Mexico_City',
        data.delivery_method || 'none',
        JSON.stringify(data.delivery_config || {}),
        userId,
      ]
    );

    logger.info('Report schedule created', { scheduleId: schedule?.id, name: data.name });

    return schedule!;
  }

  async toggleSchedule(id: string, tenantId: string, isActive: boolean): Promise<ReportSchedule> {
    const schedule = await queryOne<ReportSchedule>(
      `UPDATE reports.report_schedules
       SET is_active = $3, updated_at = NOW()
       WHERE id = $1 AND tenant_id = $2
       RETURNING *`,
      [id, tenantId, isActive]
    );

    if (!schedule) {
      throw new NotFoundError('Programación no encontrada');
    }

    return schedule;
  }

  async deleteSchedule(id: string, tenantId: string): Promise<void> {
    const result = await query(
      `DELETE FROM reports.report_schedules WHERE id = $1 AND tenant_id = $2`,
      [id, tenantId]
    );

    // Check if any row was deleted
    if (!result || result.length === 0) {
      // Try to verify it existed
      const exists = await queryOne<{ id: string }>(
        `SELECT id FROM reports.report_schedules WHERE id = $1`,
        [id]
      );
      if (!exists) {
        throw new NotFoundError('Programación no encontrada');
      }
    }
  }

  // ==================== QUICK REPORTS ====================

  async generateTrialBalance(
    tenantId: string,
    companyId: string | null,
    dateFrom: string,
    dateTo: string,
    includeZero: boolean = false
  ): Promise<any[]> {
    return query(
      `SELECT * FROM reports.generate_trial_balance($1, $2, $3, $4, $5)`,
      [tenantId, companyId, dateFrom, dateTo, includeZero]
    );
  }

  async generateGeneralLedger(
    tenantId: string,
    companyId: string | null,
    accountId: string,
    dateFrom: string,
    dateTo: string
  ): Promise<any[]> {
    return query(
      `SELECT * FROM reports.generate_general_ledger($1, $2, $3, $4, $5)`,
      [tenantId, companyId, accountId, dateFrom, dateTo]
    );
  }
}

export const reportsService = new ReportsService();
