import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { MonthlyFinancialReportORM } from '../../infrastructure/database/postgres/entities/monthly-financial-report.orm';

export interface FinancialReportDto {
  id: string;
  reportMonth: string;
  totalRevenue: number;
  assessmentRevenue: number;
  consultationRevenue: number;
  otherRevenue: number;
  totalRefunds: number;
  netRevenue: number;
  apiCost: number;
  paymentFees: number;
  otherCosts: number;
  totalCosts: number;
  grossProfit: number;
  grossMargin: number;
  totalOrders: number;
  successfulOrders: number;
  avgOrderAmount: number;
  revenueByCategory: Record<string, number>;
  revenueByChannel: Record<string, number>;
  status: string;
  confirmedBy: string | null;
  confirmedAt: Date | null;
  notes: string | null;
  createdAt: Date;
  updatedAt: Date;
}

@Injectable()
export class FinancialReportService {
  private readonly logger = new Logger(FinancialReportService.name);

  constructor(
    @InjectRepository(MonthlyFinancialReportORM)
    private readonly reportRepo: Repository<MonthlyFinancialReportORM>,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Generate monthly financial report
   */
  async generateMonthlyReport(reportMonth: string): Promise<void> {
    this.logger.log(`Generating financial report for: ${reportMonth}`);

    try {
      await this.dataSource.query(
        `
        INSERT INTO monthly_financial_reports (
          report_month,
          total_revenue, assessment_revenue, consultation_revenue, other_revenue,
          total_refunds, net_revenue,
          api_cost, payment_fees, other_costs, total_costs,
          gross_profit, gross_margin,
          total_orders, successful_orders, avg_order_amount,
          revenue_by_category, revenue_by_channel,
          status
        )
        SELECT
          $1 as report_month,

          -- Revenue from orders
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) as total_revenue,

          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID' AND service_type = 'ASSESSMENT'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) as assessment_revenue,

          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID' AND service_type = 'CONSULTATION'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) as consultation_revenue,

          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID' AND service_type NOT IN ('ASSESSMENT', 'CONSULTATION')
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) as other_revenue,

          -- Refunds
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'REFUNDED'
                    AND to_char(refunded_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) as total_refunds,

          -- Net revenue
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) -
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'REFUNDED'
                    AND to_char(refunded_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) as net_revenue,

          -- Costs (from daily_statistics)
          COALESCE((SELECT SUM(estimated_api_cost) FROM daily_statistics
                    WHERE dimension = 'OVERALL'
                    AND to_char(stat_date, 'YYYY-MM') = $1), 0) as api_cost,

          -- Payment fees (0.6% of revenue)
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) * 0.006 as payment_fees,

          0 as other_costs,

          -- Total costs
          COALESCE((SELECT SUM(estimated_api_cost) FROM daily_statistics
                    WHERE dimension = 'OVERALL'
                    AND to_char(stat_date, 'YYYY-MM') = $1), 0) +
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) * 0.006 as total_costs,

          -- Gross profit
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) -
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'REFUNDED'
                    AND to_char(refunded_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) -
          COALESCE((SELECT SUM(estimated_api_cost) FROM daily_statistics
                    WHERE dimension = 'OVERALL'
                    AND to_char(stat_date, 'YYYY-MM') = $1), 0) -
          COALESCE((SELECT SUM(paid_amount) FROM orders
                    WHERE status = 'PAID'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) * 0.006 as gross_profit,

          -- Gross margin
          CASE
            WHEN COALESCE((SELECT SUM(paid_amount) FROM orders
                           WHERE status = 'PAID'
                           AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) > 0
            THEN (
              COALESCE((SELECT SUM(paid_amount) FROM orders
                        WHERE status = 'PAID'
                        AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) -
              COALESCE((SELECT SUM(paid_amount) FROM orders
                        WHERE status = 'REFUNDED'
                        AND to_char(refunded_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) -
              COALESCE((SELECT SUM(estimated_api_cost) FROM daily_statistics
                        WHERE dimension = 'OVERALL'
                        AND to_char(stat_date, 'YYYY-MM') = $1), 0) -
              COALESCE((SELECT SUM(paid_amount) FROM orders
                        WHERE status = 'PAID'
                        AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) * 0.006
            ) / COALESCE((SELECT SUM(paid_amount) FROM orders
                          WHERE status = 'PAID'
                          AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 1)
            ELSE 0
          END as gross_margin,

          -- Order statistics
          (SELECT COUNT(*) FROM orders
           WHERE to_char(created_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1) as total_orders,

          (SELECT COUNT(*) FROM orders
           WHERE status = 'PAID'
           AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1) as successful_orders,

          COALESCE((SELECT AVG(paid_amount) FROM orders
                    WHERE status = 'PAID'
                    AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1), 0) as avg_order_amount,

          -- Revenue by category
          COALESCE((SELECT jsonb_object_agg(service_category, amount)
                    FROM (SELECT service_category, SUM(paid_amount) as amount
                          FROM orders
                          WHERE status = 'PAID'
                          AND to_char(paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1
                          AND service_category IS NOT NULL
                          GROUP BY service_category) sub), '{}') as revenue_by_category,

          -- Revenue by channel (via user source_channel)
          COALESCE((SELECT jsonb_object_agg(source_channel, amount)
                    FROM (SELECT u.source_channel, SUM(o.paid_amount) as amount
                          FROM orders o
                          JOIN users u ON o.user_id = u.id
                          WHERE o.status = 'PAID'
                          AND to_char(o.paid_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM') = $1
                          AND u.source_channel IS NOT NULL
                          GROUP BY u.source_channel) sub), '{}') as revenue_by_channel,

          'DRAFT' as status

        ON CONFLICT (report_month) DO UPDATE SET
          total_revenue = EXCLUDED.total_revenue,
          assessment_revenue = EXCLUDED.assessment_revenue,
          consultation_revenue = EXCLUDED.consultation_revenue,
          other_revenue = EXCLUDED.other_revenue,
          total_refunds = EXCLUDED.total_refunds,
          net_revenue = EXCLUDED.net_revenue,
          api_cost = EXCLUDED.api_cost,
          payment_fees = EXCLUDED.payment_fees,
          total_costs = EXCLUDED.total_costs,
          gross_profit = EXCLUDED.gross_profit,
          gross_margin = EXCLUDED.gross_margin,
          total_orders = EXCLUDED.total_orders,
          successful_orders = EXCLUDED.successful_orders,
          avg_order_amount = EXCLUDED.avg_order_amount,
          revenue_by_category = EXCLUDED.revenue_by_category,
          revenue_by_channel = EXCLUDED.revenue_by_channel,
          updated_at = NOW()
        WHERE monthly_financial_reports.status = 'DRAFT'
        `,
        [reportMonth]
      );

      this.logger.log(`Financial report generated for: ${reportMonth}`);
    } catch (error) {
      this.logger.error(`Failed to generate financial report for ${reportMonth}:`, error);
      throw error;
    }
  }

  /**
   * List financial reports
   */
  async listReports(params: {
    year?: number;
    status?: string;
  }): Promise<FinancialReportDto[]> {
    const query = this.reportRepo.createQueryBuilder('report');

    if (params.year) {
      query.andWhere("report.reportMonth LIKE :year", { year: `${params.year}%` });
    }

    if (params.status) {
      query.andWhere('report.status = :status', { status: params.status });
    }

    const reports = await query.orderBy('report.reportMonth', 'DESC').getMany();
    return reports.map(this.toDto);
  }

  /**
   * Get a specific report
   */
  async getReport(reportMonth: string): Promise<FinancialReportDto> {
    const report = await this.reportRepo.findOne({
      where: { reportMonth },
    });

    if (!report) {
      throw new NotFoundException(`Financial report not found for: ${reportMonth}`);
    }

    return this.toDto(report);
  }

  /**
   * Confirm a report
   */
  async confirmReport(reportMonth: string, adminId: string): Promise<FinancialReportDto> {
    const report = await this.reportRepo.findOne({
      where: { reportMonth },
    });

    if (!report) {
      throw new NotFoundException(`Financial report not found for: ${reportMonth}`);
    }

    if (report.status !== 'DRAFT') {
      throw new BadRequestException(`Report is already ${report.status}`);
    }

    report.status = 'CONFIRMED';
    report.confirmedBy = adminId;
    report.confirmedAt = new Date();

    const saved = await this.reportRepo.save(report);
    return this.toDto(saved);
  }

  /**
   * Lock a report
   */
  async lockReport(reportMonth: string): Promise<FinancialReportDto> {
    const report = await this.reportRepo.findOne({
      where: { reportMonth },
    });

    if (!report) {
      throw new NotFoundException(`Financial report not found for: ${reportMonth}`);
    }

    if (report.status !== 'CONFIRMED') {
      throw new BadRequestException('Only confirmed reports can be locked');
    }

    report.status = 'LOCKED';
    const saved = await this.reportRepo.save(report);
    return this.toDto(saved);
  }

  /**
   * Update report notes
   */
  async updateNotes(reportMonth: string, notes: string): Promise<FinancialReportDto> {
    const report = await this.reportRepo.findOne({
      where: { reportMonth },
    });

    if (!report) {
      throw new NotFoundException(`Financial report not found for: ${reportMonth}`);
    }

    if (report.status === 'LOCKED') {
      throw new BadRequestException('Cannot update locked report');
    }

    report.notes = notes;
    const saved = await this.reportRepo.save(report);
    return this.toDto(saved);
  }

  private toDto(orm: MonthlyFinancialReportORM): FinancialReportDto {
    return {
      id: orm.id,
      reportMonth: orm.reportMonth,
      totalRevenue: Number(orm.totalRevenue),
      assessmentRevenue: Number(orm.assessmentRevenue),
      consultationRevenue: Number(orm.consultationRevenue),
      otherRevenue: Number(orm.otherRevenue),
      totalRefunds: Number(orm.totalRefunds),
      netRevenue: Number(orm.netRevenue),
      apiCost: Number(orm.apiCost),
      paymentFees: Number(orm.paymentFees),
      otherCosts: Number(orm.otherCosts),
      totalCosts: Number(orm.totalCosts),
      grossProfit: Number(orm.grossProfit),
      grossMargin: Number(orm.grossMargin),
      totalOrders: Number(orm.totalOrders),
      successfulOrders: Number(orm.successfulOrders),
      avgOrderAmount: Number(orm.avgOrderAmount),
      revenueByCategory: orm.revenueByCategory || {},
      revenueByChannel: orm.revenueByChannel || {},
      status: orm.status,
      confirmedBy: orm.confirmedBy,
      confirmedAt: orm.confirmedAt,
      notes: orm.notes,
      createdAt: orm.createdAt,
      updatedAt: orm.updatedAt,
    };
  }
}
