import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { EUserStatus } from "@tria-plc/api-common/utils/enums/user.enum";
import { Employee } from "@tria-plc/iamapi-common";
import { User } from "@tria-plc/iamapi-common/entities/iam/user/user.entity";
import { Freight } from "@edr/types";
import { Repository, ObjectLiteral } from "typeorm";

import { Booking } from "../bookings/entities/booking.entity";
import { Cargo } from "../cargoes/entities/cargoes.entity";
import { Container } from "../container-management/entities/container.entity";
import { PaymentEntity } from "../payment/entities/payment.entity";
import { Train } from "../trains/entities/train.entity";
import { Wagon } from "../wagons/entities/wagon.entity";
import {
  OVERVIEW_CLOSED_STATUSES,
  OVERVIEW_IN_APPROVAL_STATUSES,
  OVERVIEW_NEEDS_ACTION_STATUSES,
  OVERVIEW_URGENT_PRIORITY_THRESHOLD,
} from "./overview.constants";
import { Company } from "../companies/entities/company.entity";

export type OverviewBookingKpisRow = {
  totalActive: number;
  needsAction: number;
  urgent: number;
  inApproval: number;
  submittedToday: number;
};

export type OverviewRecentBookingRow = {
  id: string;
  reference: string;
  customerLabel: string;
  status: string;
  priorityScore: number;
  totalAmount: number | null;
  paymentCurrency: string | null;
  createdAt: Date;
};

@Injectable()
export class OverviewRepository {
  constructor(
    @InjectRepository(Booking)
    private readonly bookingRepository: Repository<Booking>,
    @InjectRepository(PaymentEntity)
    private readonly paymentRepository: Repository<PaymentEntity>,
    @InjectRepository(Company)
    private readonly companyRepository: Repository<Company>,
    @InjectRepository(Train)
    private readonly trainRepository: Repository<Train>,
    @InjectRepository(Wagon)
    private readonly wagonRepository: Repository<Wagon>,
    @InjectRepository(Container)
    private readonly containerRepository: Repository<Container>,
    @InjectRepository(Cargo)
    private readonly cargoRepository: Repository<Cargo>,
    @InjectRepository(Employee)
    private readonly employeeRepository: Repository<Employee>,
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) { }

  async getBookingKpis(): Promise<OverviewBookingKpisRow> {
    const row = await this.bookingRepository
      .createQueryBuilder("booking")
      .select(
        `COUNT(*) FILTER (WHERE booking.status NOT IN (:...closedStatuses) AND booking.status != 'DRAFT')::int`,
        "totalActive",
      )
      .addSelect(
        `COUNT(*) FILTER (WHERE booking.status IN (:...needsActionStatuses))::int`,
        "needsAction",
      )
      .addSelect(
        `COUNT(*) FILTER (WHERE booking.priority_score >= :urgentThreshold)::int`,
        "urgent",
      )
      .addSelect(
        `COUNT(*) FILTER (WHERE booking.status IN (:...inApprovalStatuses))::int`,
        "inApproval",
      )
      .addSelect(
        `COUNT(*) FILTER (WHERE booking.created_at >= CURRENT_DATE AND booking.status != 'DRAFT')::int`,
        "submittedToday",
      )
      .where("booking.deleted_at IS NULL")
      .setParameters({
        closedStatuses: [...OVERVIEW_CLOSED_STATUSES],
        needsActionStatuses: [...OVERVIEW_NEEDS_ACTION_STATUSES],
        inApprovalStatuses: [...OVERVIEW_IN_APPROVAL_STATUSES],
        urgentThreshold: OVERVIEW_URGENT_PRIORITY_THRESHOLD,
      })
      .getRawOne<Record<string, string>>();

    return {
      totalActive: Number(row?.totalActive ?? 0),
      needsAction: Number(row?.needsAction ?? 0),
      urgent: Number(row?.urgent ?? 0),
      inApproval: Number(row?.inApproval ?? 0),
      submittedToday: Number(row?.submittedToday ?? 0),
    };
  }

  async getOperationsKpis(): Promise<{
    trainsActive: number;
    wagonsAvailable: number;
    containersInTransit: number;
    cargoesLoaded: number;
  }> {
    const [trainsActive, wagonsAvailable, containersInTransit, cargoesLoaded] =
      await Promise.all([
        this.trainRepository
          .createQueryBuilder("train")
          .where("train.deleted_at IS NULL")
          .andWhere("train.status IN (:...statuses)", {
            statuses: [
              Freight.TrainStatus.InService,
              Freight.TrainStatus.Scheduled,
            ],
          })
          .getCount(),
        this.wagonRepository
          .createQueryBuilder("wagon")
          .where("wagon.deleted_at IS NULL")
          .andWhere("wagon.status = :status", {
            status: Freight.WagonStatus.Available,
          })
          .getCount(),
        this.containerRepository
          .createQueryBuilder("container")
          .where("container.deleted_at IS NULL")
          .andWhere("container.status = :status", { status: "IN_TRANSIT" })
          .getCount(),
        this.cargoRepository
          .createQueryBuilder("cargo")
          .where("cargo.deleted_at IS NULL")
          .andWhere("cargo.status IN (:...statuses)", {
            statuses: ["LOADED", "IN_TRANSIT"],
          })
          .getCount(),
      ]);

    return {
      trainsActive,
      wagonsAvailable,
      containersInTransit,
      cargoesLoaded,
    };
  }

  async getCustomerKpis(): Promise<{
    totalCustomers: number;
    newCustomersThisMonth: number;
  }> {
    const row = await this.companyRepository
      .createQueryBuilder("customer")
      .select("COUNT(*)::int", "totalCustomers")
      .addSelect(
        `COUNT(*) FILTER (WHERE customer.created_at >= date_trunc('month', CURRENT_DATE))::int`,
        "newCustomersThisMonth",
      )
      .where("customer.deleted_at IS NULL")
      .getRawOne<Record<string, string>>();

    return {
      totalCustomers: Number(row?.totalCustomers ?? 0),
      newCustomersThisMonth: Number(row?.newCustomersThisMonth ?? 0),
    };
  }

  async getBillingKpis(): Promise<{
    revenueMtdEtb: number;
    revenueMtdUsd: number;
    pendingPayments: number;
    successfulPaymentsMtd: number;
  }> {
    const revenueRow = await this.paymentRepository
      .createQueryBuilder("payment")
      .select(
        `COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'ETB'), 0)`,
        "revenueMtdEtb",
      )
      .addSelect(
        `COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'USD'), 0)`,
        "revenueMtdUsd",
      )
      .addSelect(`COUNT(*)::int`, "successfulPaymentsMtd")
      .where("payment.status = :status", { status: "success" })
      .andWhere(
        `COALESCE(payment.paid_at, payment.created_at) >= date_trunc('month', CURRENT_DATE)`,
      )
      .getRawOne<Record<string, string>>();

    const pendingPayments = await this.paymentRepository
      .createQueryBuilder("payment")
      .where("payment.status IN (:...statuses)", {
        statuses: ["action-required", "processing"],
      })
      .getCount();

    return {
      revenueMtdEtb: Number(revenueRow?.revenueMtdEtb ?? 0),
      revenueMtdUsd: Number(revenueRow?.revenueMtdUsd ?? 0),
      pendingPayments,
      successfulPaymentsMtd: Number(revenueRow?.successfulPaymentsMtd ?? 0),
    };
  }

  async getStaffKpis(): Promise<{
    activeEmployees: number;
    activeUsers: number;
  }> {
    const [activeEmployees, activeUsers] = await Promise.all([
      this.employeeRepository.count({
        where: { isCurrent: true },
      }),
      this.userRepository.count({
        where: {
          isActive: true,
          status: EUserStatus.ACCEPTED,
        },
      }),
    ]);

    return { activeEmployees, activeUsers };
  }

  async getBookingTrend(
    days: number,
  ): Promise<{ date: string; count: number }[]> {
    const rows = await this.bookingRepository
      .createQueryBuilder("booking")
      .select(`to_char(booking.created_at::date, 'YYYY-MM-DD')`, "date")
      .addSelect("COUNT(*)::int", "count")
      .where("booking.deleted_at IS NULL")
      .andWhere(`booking.created_at >= CURRENT_DATE - :days::int + 1`, { days })
      .groupBy("booking.created_at::date")
      .orderBy("booking.created_at::date", "ASC")
      .getRawMany<{ date: string; count: string }>();

    return rows.map((row) => ({
      date: row.date,
      count: Number(row.count),
    }));
  }

  async getStatusCounts(): Promise<Record<string, number>> {
    const rows = await this.bookingRepository
      .createQueryBuilder("booking")
      .select("booking.status", "status")
      .addSelect("COUNT(*)::int", "count")
      .where("booking.deleted_at IS NULL")
      .groupBy("booking.status")
      .getRawMany<{ status: string; count: string }>();

    return Object.fromEntries(
      rows.map((row) => [row.status, Number(row.count)]),
    );
  }

  async getPaymentTrend(
    days: number,
  ): Promise<{ date: string; amountEtb: number; amountUsd: number }[]> {
    const rows = await this.paymentRepository
      .createQueryBuilder("payment")
      .select(
        `to_char(COALESCE(payment.paid_at, payment.created_at)::date, 'YYYY-MM-DD')`,
        "date",
      )
      .addSelect(
        `COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'ETB'), 0)`,
        "amountEtb",
      )
      .addSelect(
        `COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'USD'), 0)`,
        "amountUsd",
      )
      .where("payment.status = :status", { status: "success" })
      .andWhere(
        `COALESCE(payment.paid_at, payment.created_at) >= CURRENT_DATE - :days::int + 1`,
        { days },
      )
      .groupBy(`COALESCE(payment.paid_at, payment.created_at)::date`)
      .orderBy(`COALESCE(payment.paid_at, payment.created_at)::date`, "ASC")
      .getRawMany<{ date: string; amountEtb: string; amountUsd: string }>();

    return rows.map((row) => ({
      date: row.date,
      amountEtb: Number(row.amountEtb),
      amountUsd: Number(row.amountUsd),
    }));
  }

  async getRecentBookings(limit: number): Promise<OverviewRecentBookingRow[]> {
    const rows = await this.bookingRepository
      .createQueryBuilder("booking")
      .leftJoin("booking.company", "company")
      .select("booking.id", "id")
      .addSelect("booking.reference", "reference")
      .addSelect("COALESCE(company.name, '—')", "customerLabel")
      .addSelect("booking.status", "status")
      .addSelect("booking.priority_score", "priorityScore")
      .addSelect("booking.total_amount", "totalAmount")
      .addSelect("booking.payment_currency", "paymentCurrency")
      .addSelect("booking.created_at", "createdAt")
      .where("booking.deleted_at IS NULL")
      .orderBy("booking.created_at", "DESC")
      .limit(limit)
      .getRawMany<{
        id: string;
        reference: string;
        customerLabel: string;
        status: string;
        priorityScore: string;
        totalAmount: string | null;
        paymentCurrency: string | null;
        createdAt: Date;
      }>();

    return rows.map((row) => ({
      id: row.id,
      reference: row.reference,
      customerLabel: row.customerLabel,
      status: row.status,
      priorityScore: Number(row.priorityScore),
      totalAmount: row.totalAmount != null ? Number(row.totalAmount) : null,
      paymentCurrency: row.paymentCurrency,
      createdAt: row.createdAt,
    }));
  }

  async getBookingsByFreightType(): Promise<
    { label: string; count: number }[]
  > {
    const rows = await this.bookingRepository
      .createQueryBuilder("booking")
      .select("booking.freight_type", "label")
      .addSelect("COUNT(*)::int", "count")
      .where("booking.deleted_at IS NULL")
      .andWhere("booking.status != 'DRAFT'")
      .groupBy("booking.freight_type")
      .orderBy("count", "DESC")
      .getRawMany<{ label: string; count: string }>();

    return rows.map((row) => ({
      label: row.label,
      count: Number(row.count),
    }));
  }

  async getBookingsByCurrency(): Promise<{ label: string; count: number }[]> {
    const rows = await this.bookingRepository
      .createQueryBuilder("booking")
      .select("booking.payment_currency", "label")
      .addSelect("COUNT(*)::int", "count")
      .where("booking.deleted_at IS NULL")
      .andWhere("booking.status != 'DRAFT'")
      .groupBy("booking.payment_currency")
      .orderBy("count", "DESC")
      .getRawMany<{ label: string; count: string }>();

    return rows.map((row) => ({
      label: row.label,
      count: Number(row.count),
    }));
  }

  async getPaymentsByStatus(): Promise<{ status: string; count: number }[]> {
    const rows = await this.paymentRepository
      .createQueryBuilder("payment")
      .select("payment.status", "status")
      .addSelect("COUNT(*)::int", "count")
      .groupBy("payment.status")
      .orderBy("count", "DESC")
      .getRawMany<{ status: string; count: string }>();

    return rows.map((row) => ({
      status: row.status,
      count: Number(row.count),
    }));
  }

  async getPaymentsByMethod(): Promise<
    { method: string; count: number; amountEtb: number; amountUsd: number }[]
  > {
    const rows = await this.paymentRepository
      .createQueryBuilder("payment")
      .select("payment.method", "method")
      .addSelect("COUNT(*)::int", "count")
      .addSelect(
        `COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'ETB' AND payment.status = 'success'), 0)`,
        "amountEtb",
      )
      .addSelect(
        `COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'USD' AND payment.status = 'success'), 0)`,
        "amountUsd",
      )
      .groupBy("payment.method")
      .orderBy("count", "DESC")
      .getRawMany<{
        method: string;
        count: string;
        amountEtb: string;
        amountUsd: string;
      }>();

    return rows.map((row) => ({
      method: row.method,
      count: Number(row.count),
      amountEtb: Number(row.amountEtb),
      amountUsd: Number(row.amountUsd),
    }));
  }

  async getRevenueByCurrency(): Promise<
    { currency: string; amount: number }[]
  > {
    const rows = await this.paymentRepository
      .createQueryBuilder("payment")
      .select("payment.currency", "currency")
      .addSelect("COALESCE(SUM(payment.amount), 0)", "amount")
      .where("payment.status = :status", { status: "success" })
      .andWhere(
        `COALESCE(payment.paid_at, payment.created_at) >= date_trunc('month', CURRENT_DATE)`,
      )
      .groupBy("payment.currency")
      .getRawMany<{ currency: string; amount: string }>();

    return rows.map((row) => ({
      currency: row.currency,
      amount: Number(row.amount),
    }));
  }

  async getTrainStatusBreakdown(): Promise<
    { status: string; count: number }[]
  > {
    return this.statusBreakdown(this.trainRepository, "train");
  }

  async getWagonStatusBreakdown(): Promise<
    { status: string; count: number }[]
  > {
    return this.statusBreakdown(this.wagonRepository, "wagon");
  }

  async getContainerStatusBreakdown(): Promise<
    { status: string; count: number }[]
  > {
    return this.statusBreakdown(this.containerRepository, "container");
  }

  async getCargoStatusBreakdown(): Promise<
    { status: string; count: number }[]
  > {
    return this.statusBreakdown(this.cargoRepository, "cargo");
  }

  private async statusBreakdown(
    repository: Repository<ObjectLiteral>,
    alias: string,
  ): Promise<{ status: string; count: number }[]> {
    const rows = await repository
      .createQueryBuilder(alias)
      .select(`${alias}.status`, "status")
      .addSelect("COUNT(*)::int", "count")
      .where(`${alias}.deleted_at IS NULL`)
      .groupBy(`${alias}.status`)
      .orderBy("count", "DESC")
      .getRawMany<{ status: string; count: string }>();

    return rows.map((row) => ({
      status: row.status,
      count: Number(row.count),
    }));
  }

  async getCustomerGrowthTrend(
    days: number,
  ): Promise<{ date: string; count: number }[]> {
    const rows = await this.companyRepository
      .createQueryBuilder("customer")
      .select(`to_char(customer.created_at::date, 'YYYY-MM-DD')`, "date")
      .addSelect("COUNT(*)::int", "count")
      .where("customer.deleted_at IS NULL")
      .andWhere(`customer.created_at >= CURRENT_DATE - :days::int + 1`, {
        days,
      })
      .groupBy("customer.created_at::date")
      .orderBy("customer.created_at::date", "ASC")
      .getRawMany<{ date: string; count: string }>();

    return rows.map((row) => ({
      date: row.date,
      count: Number(row.count),
    }));
  }

  async getCustomersByType(): Promise<{ label: string; count: number }[]> {
    const rows = await this.companyRepository
      .createQueryBuilder("customer")
      .select(
        `COALESCE(NULLIF(customer.type, ''), 'Unknown')`,
        "label",
      )
      .addSelect("COUNT(*)::int", "count")
      .where("customer.deleted_at IS NULL")
      .groupBy("customer.type")
      .orderBy("count", "DESC")
      .getRawMany<{ label: string; count: string }>();

    return rows.map((row) => ({
      label: row.label,
      count: Number(row.count),
    }));
  }

  async getTopCustomersByBookings(
    limit: number,
  ): Promise<{ label: string; count: number }[]> {
    const rows = await this.bookingRepository
      .createQueryBuilder("booking")
      .leftJoin("booking.company", "company")
      .select(`COALESCE(company.name, 'Unknown')`, "label")
      .addSelect("COUNT(*)::int", "count")
      .where("booking.deleted_at IS NULL")
      .andWhere("booking.status != 'DRAFT'")
      .groupBy("company.name")
      .orderBy("count", "DESC")
      .limit(limit)
      .getRawMany<{ label: string; count: string }>();

    return rows.map((row) => ({
      label: row.label,
      count: Number(row.count),
    }));
  }

  async getUsersByStatus(): Promise<{ status: string; count: number }[]> {
    const rows = await this.userRepository
      .createQueryBuilder("user")
      .select("user.status", "status")
      .addSelect("COUNT(*)::int", "count")
      .groupBy("user.status")
      .orderBy("count", "DESC")
      .getRawMany<{ status: string; count: string }>();

    return rows.map((row) => ({
      status: row.status,
      count: Number(row.count),
    }));
  }

  async getEmployeeGrowthTrend(
    days: number,
  ): Promise<{ date: string; count: number }[]> {
    const rows = await this.employeeRepository
      .createQueryBuilder("employee")
      .select(`to_char(employee.created_at::date, 'YYYY-MM-DD')`, "date")
      .addSelect("COUNT(*)::int", "count")
      .where("employee.is_current = true")
      .andWhere(`employee.created_at >= CURRENT_DATE - :days::int + 1`, {
        days,
      })
      .groupBy("employee.created_at::date")
      .orderBy("employee.created_at::date", "ASC")
      .getRawMany<{ date: string; count: string }>();

    return rows.map((row) => ({
      date: row.date,
      count: Number(row.count),
    }));
  }

  async getActiveUsersBreakdown(): Promise<{ label: string; count: number }[]> {
    const [active, inactive] = await Promise.all([
      this.userRepository.count({
        where: { isActive: true, status: EUserStatus.ACCEPTED },
      }),
      this.userRepository
        .createQueryBuilder("user")
        .where("user.is_active = false OR user.status != :status", {
          status: EUserStatus.ACCEPTED,
        })
        .getCount(),
    ]);

    return [
      { label: "Active", count: active },
      { label: "Inactive", count: inactive },
    ];
  }
}
