import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';

import { Booking } from '../bookings/entities/booking.entity';

/** Booking statuses that represent a delivered/finished shipment. */
const DELIVERED_STATUSES = ['DELIVERED', 'COMPLETED'] as const;

/**
 * Statuses that represent real, committed freight (excludes drafts and dead
 * bookings) — used for tonnage so cancelled/expired drafts don't inflate volume.
 */
const COMMITTED_STATUSES = [
  'APPROVED',
  'CONTRACT_READY',
  'SIGNED_CUSTOMER',
  'FULLY_EXECUTED',
  'SELECTED_FOR_BATCH',
  'PNR_GENERATED',
  'PAYMENT_VERIFICATION_IN_PROGRESS',
  'PAID',
  'IN_TRANSIT',
  'COMPLETED',
  'DELIVERED',
  'CONSOLIDATED',
] as const;

export interface CurrencyTotal {
  currency: string;
  total: number;
}

/**
 * What the dashboard is scoped to: a single operational profile (the active
 * importer/exporter mode) when one resolves, otherwise the whole company
 * (legacy / not-yet-onboarded fallback).
 */
export type DashboardScope =
  | { companyProfileId: string }
  | { companyId: string };

/** Apply the scope as a WHERE clause on a bookings query builder. */
function applyScope(
  qb: SelectQueryBuilder<Booking>,
  scope: DashboardScope,
): SelectQueryBuilder<Booking> {
  return 'companyProfileId' in scope
    ? qb.where('b.company_profile_id = :companyProfileId', {
        companyProfileId: scope.companyProfileId,
      })
    : qb.where('b.company_id = :companyId', { companyId: scope.companyId });
}

export interface MonthlyTonnage {
  year: number;
  month: number; // 1-12
  tonnes: number;
}

/**
 * Read-only aggregation queries against the bookings table, scoped to a
 * company, that back the portal dashboard. Lives in the companies module so it
 * can be exposed via `companies.controller` without a circular dependency on
 * BookingsModule (which already imports CompaniesModule).
 */
@Injectable()
export class CompanyDashboardRepository {
  constructor(
    @InjectRepository(Booking)
    private readonly bookings: Repository<Booking>,
  ) {}

  /** Count of delivered/completed bookings within [from, to) for the scope. */
  async countDelivered(scope: DashboardScope, from: Date, to: Date): Promise<number> {
    return applyScope(this.bookings.createQueryBuilder('b'), scope)
      .andWhere('b.deleted_at IS NULL')
      .andWhere('b.status IN (:...statuses)', { statuses: [...DELIVERED_STATUSES] })
      .andWhere('b.created_at >= :from AND b.created_at < :to', { from, to })
      .getCount();
  }

  /** Count of committed (non-draft, non-dead) bookings within [from, to) for the scope. */
  async countCommitted(scope: DashboardScope, from: Date, to: Date): Promise<number> {
    return applyScope(this.bookings.createQueryBuilder('b'), scope)
      .andWhere('b.deleted_at IS NULL')
      .andWhere('b.status IN (:...statuses)', { statuses: [...COMMITTED_STATUSES] })
      .andWhere('b.created_at >= :from AND b.created_at < :to', { from, to })
      .getCount();
  }

  /** Sum of paid booking totals, grouped by currency, within [from, to) for the scope. */
  async sumPaidSpendByCurrency(scope: DashboardScope, from: Date, to: Date): Promise<CurrencyTotal[]> {
    const rows = await applyScope(
      this.bookings
        .createQueryBuilder('b')
        .select('b.payment_currency', 'currency')
        .addSelect('COALESCE(SUM(b.total_amount), 0)', 'total'),
      scope,
    )
      .andWhere('b.deleted_at IS NULL')
      .andWhere("b.payment_status = 'PAID'")
      .andWhere('b.created_at >= :from AND b.created_at < :to', { from, to })
      .groupBy('b.payment_currency')
      .getRawMany<{ currency: string; total: string }>();

    return rows.map((r) => ({ currency: r.currency ?? 'ETB', total: Number(r.total) }));
  }

  /** Total committed tonnage (cargo VGM) within [from, to) for the scope. */
  async sumCommittedTonnage(scope: DashboardScope, from: Date, to: Date): Promise<number> {
    const row = await applyScope(
      this.bookings
        .createQueryBuilder('b')
        .select('COALESCE(SUM(b.cargo_total_weight_vgm), 0)', 'total'),
      scope,
    )
      .andWhere('b.deleted_at IS NULL')
      .andWhere('b.status IN (:...statuses)', { statuses: [...COMMITTED_STATUSES] })
      .andWhere('b.created_at >= :from AND b.created_at < :to', { from, to })
      .getRawOne<{ total: string }>();

    return Number(row?.total ?? 0);
  }

  /** Committed tonnage grouped by calendar month within [from, to) for the scope. */
  async monthlyCommittedTonnage(scope: DashboardScope, from: Date, to: Date): Promise<MonthlyTonnage[]> {
    const rows = await applyScope(
      this.bookings
        .createQueryBuilder('b')
        .select('EXTRACT(YEAR FROM b.created_at)', 'year')
        .addSelect('EXTRACT(MONTH FROM b.created_at)', 'month')
        .addSelect('COALESCE(SUM(b.cargo_total_weight_vgm), 0)', 'total'),
      scope,
    )
      .andWhere('b.deleted_at IS NULL')
      .andWhere('b.status IN (:...statuses)', { statuses: [...COMMITTED_STATUSES] })
      .andWhere('b.created_at >= :from AND b.created_at < :to', { from, to })
      .groupBy('year')
      .addGroupBy('month')
      .getRawMany<{ year: string; month: string; total: string }>();

    return rows.map((r) => ({
      year: Number(r.year),
      month: Number(r.month),
      tonnes: Number(r.total),
    }));
  }
}
