import { and, asc, desc, eq, inArray, isNull } from 'drizzle-orm';
import {
  endOfDay,
  endOfWeek,
  formatISO,
  isAfter,
  isBefore,
  isSameDay,
  startOfDay,
  startOfWeek
} from 'date-fns';
import {
  crmApprovalRequests,
  crmCustomers,
  crmEnquiries,
  crmEnquiryFollowups,
  crmQuotationFollowups,
  crmQuotations,
  memberships,
  users
} from '@/db/schema';
import { db } from '@/lib/db';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { getUserBranches } from '@/features/foundation/branch-scope/service';
import { listApprovalRequests } from '@/features/foundation/approval/server/service';
import { getActiveOptionsByCategory } from '@/features/foundation/master-options/service';
import {
  getRevenueByBillingCustomer,
  getRevenueByConsultant,
  getRevenueByContractor,
  getRevenueByEndCustomer
} from '@/features/crm/reporting/server/service';
import { getPipelineStageThaiLabel, getProjectPartyRoleThaiLabel } from '@/features/crm/shared/terminology';
import type {
  CrmDashboardApprovalRow,
  CrmDashboardApprovalSummary,
  CrmDashboardFilters,
  CrmDashboardFollowupRow,
  CrmDashboardFollowupSummary,
  CrmDashboardFunnelStep,
  CrmDashboardHotProjectRow,
  CrmDashboardReferenceData,
  CrmDashboardResponse,
  CrmDashboardRevenueRow,
  CrmDashboardSalesRankingRow,
  CrmDashboardSummary
} from '../api/types';

const ENQUIRY_STATUS_CATEGORY = 'crm_enquiry_status';
const QUOTATION_STATUS_CATEGORY = 'crm_quotation_status';
const PRODUCT_TYPE_CATEGORY = 'crm_product_type';
const PROJECT_PARTY_ROLE_CATEGORY = 'crm_project_party_role';

type DashboardContext = {
  organizationId: string;
  userId: string;
  membershipRole: string;
  businessRole: string;
  permissions: string[];
};

type NormalizedFilters = {
  dateFrom: string;
  dateTo: string;
  branch: string | null;
  salesman: string | null;
  projectPartyRole: string | null;
  productType: string | null;
};

type StatusMaps = {
  enquiryStatusById: Map<string, string>;
  quotationStatusById: Map<string, string>;
};

const DASHBOARD_FULL_ACCESS_ROLES = new Set([
  'marketing',
  'sales_manager',
  'department_manager',
  'top_manager'
]);

const DASHBOARD_OWNED_SCOPE_ROLES = new Set(['sales', 'sales_support']);

function normalizeDashboardFilters(filters: CrmDashboardFilters): NormalizedFilters {
  const now = new Date();
  const monthStart = startOfDay(new Date(now.getFullYear(), now.getMonth(), 1));

  return {
    dateFrom: filters.dateFrom ?? formatISO(monthStart, { representation: 'date' }),
    dateTo: filters.dateTo ?? formatISO(now, { representation: 'date' }),
    branch: filters.branch ?? null,
    salesman: filters.salesman ?? null,
    projectPartyRole: filters.projectPartyRole ?? null,
    productType: filters.productType ?? null
  };
}

function toDayBounds(filters: NormalizedFilters) {
  return {
    from: startOfDay(new Date(filters.dateFrom)),
    to: endOfDay(new Date(filters.dateTo))
  };
}

function round2(value: number) {
  return Math.round(value * 100) / 100;
}

function average(values: number[]) {
  if (values.length === 0) {
    return 0;
  }

  return round2(values.reduce((sum, value) => sum + value, 0) / values.length);
}

function canViewCommercialData(context: DashboardContext) {
  return (
    context.membershipRole === 'admin' || context.permissions.includes(PERMISSIONS.crmQuotationRead)
  );
}

function canViewApprovalData(context: DashboardContext) {
  return (
    context.membershipRole === 'admin' || context.permissions.includes(PERMISSIONS.crmApprovalRead)
  );
}

function hasDashboardFullScope(context: DashboardContext) {
  return (
    context.membershipRole === 'admin' || DASHBOARD_FULL_ACCESS_ROLES.has(context.businessRole)
  );
}

function canAccessDashboardEnquiry(
  row: typeof crmEnquiries.$inferSelect,
  context: DashboardContext
) {
  if (hasDashboardFullScope(context)) {
    return true;
  }

  if (DASHBOARD_OWNED_SCOPE_ROLES.has(context.businessRole)) {
    return row.createdBy === context.userId || row.assignedToUserId === context.userId;
  }

  return true;
}

function canAccessDashboardQuotation(
  row: typeof crmQuotations.$inferSelect,
  context: DashboardContext
) {
  if (hasDashboardFullScope(context)) {
    return true;
  }

  if (DASHBOARD_OWNED_SCOPE_ROLES.has(context.businessRole)) {
    return row.salesmanId === context.userId;
  }

  return true;
}

async function getStatusMaps(organizationId: string): Promise<StatusMaps> {
  const [enquiryOptions, quotationOptions] = await Promise.all([
    getActiveOptionsByCategory(ENQUIRY_STATUS_CATEGORY, { organizationId }),
    getActiveOptionsByCategory(QUOTATION_STATUS_CATEGORY, { organizationId })
  ]);

  return {
    enquiryStatusById: new Map(enquiryOptions.map((item) => [item.id, item.code])),
    quotationStatusById: new Map(quotationOptions.map((item) => [item.id, item.code]))
  };
}

async function getReferenceData(organizationId: string): Promise<CrmDashboardReferenceData> {
  const [branches, salesmen, productTypes, projectPartyRoles] = await Promise.all([
    getUserBranches(),
    db
      .select({ id: users.id, name: users.name })
      .from(memberships)
      .innerJoin(users, eq(memberships.userId, users.id))
      .where(eq(memberships.organizationId, organizationId))
      .orderBy(asc(users.name)),
    getActiveOptionsByCategory(PRODUCT_TYPE_CATEGORY, { organizationId }),
    getActiveOptionsByCategory(PROJECT_PARTY_ROLE_CATEGORY, { organizationId })
  ]);

  return {
    branches: branches.map((item) => ({ id: item.id, code: item.code, label: item.name })),
    salesmen: salesmen.map((item) => ({ id: item.id, name: item.name })),
    productTypes: productTypes.map((item) => ({
      id: item.id,
      code: item.code,
      label: item.label
    })),
    projectPartyRoles: projectPartyRoles.map((item) => ({
      id: item.id,
      code: item.code,
      label: item.label
    }))
  };
}

function matchesEnquiryFilters(
  row: typeof crmEnquiries.$inferSelect,
  filters: NormalizedFilters
) {
  const { from, to } = toDayBounds(filters);

  if (filters.branch && row.branchId !== filters.branch) return false;
  if (filters.salesman && row.assignedToUserId !== filters.salesman) return false;
  if (filters.productType && row.productType !== filters.productType) return false;
  if (isBefore(row.createdAt, from) || isAfter(row.createdAt, to)) return false;

  return true;
}

function matchesQuotationFilters(
  row: typeof crmQuotations.$inferSelect,
  filters: NormalizedFilters
) {
  const { from, to } = toDayBounds(filters);

  if (filters.branch && row.branchId !== filters.branch) return false;
  if (filters.salesman && row.salesmanId !== filters.salesman) return false;
  if (filters.productType && row.quotationType !== filters.productType) return false;
  if (isBefore(row.quotationDate, from) || isAfter(row.quotationDate, to)) return false;

  return true;
}

function isPipelineStage(
  enquiry: typeof crmEnquiries.$inferSelect,
  stage: 'lead' | 'enquiry' | 'closed_won' | 'closed_lost'
) {
  return enquiry.pipelineStage === stage;
}

async function loadScopedRows(
  context: DashboardContext,
  filters: NormalizedFilters
) {
  const [enquiries, quotations] = await Promise.all([
    db
      .select()
      .from(crmEnquiries)
      .where(
        and(eq(crmEnquiries.organizationId, context.organizationId), isNull(crmEnquiries.deletedAt))
      ),
    db
      .select()
      .from(crmQuotations)
      .where(
        and(eq(crmQuotations.organizationId, context.organizationId), isNull(crmQuotations.deletedAt))
      )
  ]);

  return {
    enquiries: enquiries
      .filter((row) => canAccessDashboardEnquiry(row, context))
      .filter((row) => matchesEnquiryFilters(row, filters)),
    quotations: quotations
      .filter((row) => canAccessDashboardQuotation(row, context))
      .filter((row) => matchesQuotationFilters(row, filters))
  };
}

function buildSummary(
  scopedEnquiries: Array<typeof crmEnquiries.$inferSelect>,
  scopedQuotations: Array<typeof crmQuotations.$inferSelect>,
  statusMaps: StatusMaps
): CrmDashboardSummary {
  const now = new Date();
  const leadRows = scopedEnquiries.filter((row) => isPipelineStage(row, 'lead'));
  const enquiryRows = scopedEnquiries.filter((row) => isPipelineStage(row, 'enquiry'));
  const wonRows = scopedEnquiries.filter((row) => isPipelineStage(row, 'closed_won'));
  const lostRows = scopedEnquiries.filter((row) => isPipelineStage(row, 'closed_lost'));

  const leadAges = leadRows.map((row) => (now.getTime() - row.createdAt.getTime()) / (1000 * 60 * 60 * 24));
  const quotationStatusCodes = scopedQuotations.map((row) => statusMaps.quotationStatusById.get(row.status) ?? '');

  return {
    lead: {
      leadCount: leadRows.length,
      newLeads: leadRows.length,
      unassignedLeads: leadRows.filter((row) => row.assignedToUserId === null).length,
      averageLeadAgingDays: average(leadAges)
    },
    enquiry: {
      enquiryCount: enquiryRows.length,
      activeEnquiries: enquiryRows.length,
      wonCount: wonRows.length,
      lostCount: lostRows.length,
      hotEnquiries: enquiryRows.filter((row) => row.isHotProject).length,
      enquiryValue: round2(enquiryRows.reduce((sum, row) => sum + (row.estimatedValue ?? 0), 0))
    },
    quotation: {
      draftQuotations: quotationStatusCodes.filter((code) => code === 'draft').length,
      pendingApproval: quotationStatusCodes.filter((code) => code === 'pending_approval').length,
      approvedQuotations: quotationStatusCodes.filter((code) => code === 'approved').length,
      sentQuotations: quotationStatusCodes.filter((code) => code === 'sent').length
    },
    revenue: {
      quotationValue: round2(
        scopedQuotations
          .filter((row) => (statusMaps.quotationStatusById.get(row.status) ?? '') !== 'cancelled')
          .reduce((sum, row) => sum + row.totalAmount, 0)
      ),
      wonValue: round2(
        scopedQuotations
          .filter((row) => (statusMaps.quotationStatusById.get(row.status) ?? '') === 'accepted')
          .reduce((sum, row) => sum + row.totalAmount, 0)
      ),
      lostValue: round2(
        scopedQuotations
          .filter((row) =>
            ['lost', 'rejected'].includes(statusMaps.quotationStatusById.get(row.status) ?? '')
          )
          .reduce((sum, row) => sum + row.totalAmount, 0)
      )
    }
  };
}

function buildFunnel(
  summary: CrmDashboardSummary,
  scopedQuotations: Array<typeof crmQuotations.$inferSelect>,
  statusMaps: StatusMaps
): CrmDashboardFunnelStep[] {
  const steps = [
    {
      key: 'lead',
      label: getPipelineStageThaiLabel('lead'),
      count: summary.lead.leadCount,
      value: 0
    },
    {
      key: 'enquiry',
      label: getPipelineStageThaiLabel('enquiry'),
      count: summary.enquiry.enquiryCount,
      value: summary.enquiry.enquiryValue
    },
    {
      key: 'quotation',
      label: 'ใบเสนอราคา',
      count: scopedQuotations.filter(
        (row) => (statusMaps.quotationStatusById.get(row.status) ?? '') !== 'cancelled'
      ).length,
      value: scopedQuotations
        .filter((row) => (statusMaps.quotationStatusById.get(row.status) ?? '') !== 'cancelled')
        .reduce((sum, row) => sum + row.totalAmount, 0)
    },
    {
      key: 'pending_approval',
      label: 'รออนุมัติ',
      count: scopedQuotations.filter(
        (row) => (statusMaps.quotationStatusById.get(row.status) ?? '') === 'pending_approval'
      ).length,
      value: scopedQuotations
        .filter((row) => (statusMaps.quotationStatusById.get(row.status) ?? '') === 'pending_approval')
        .reduce((sum, row) => sum + row.totalAmount, 0)
    },
    {
      key: 'approved',
      label: 'อนุมัติแล้ว',
      count: scopedQuotations.filter(
        (row) => (statusMaps.quotationStatusById.get(row.status) ?? '') === 'approved'
      ).length,
      value: scopedQuotations
        .filter((row) => (statusMaps.quotationStatusById.get(row.status) ?? '') === 'approved')
        .reduce((sum, row) => sum + row.totalAmount, 0)
    },
    {
      key: 'closed_won',
      label: getPipelineStageThaiLabel('closed_won'),
      count: summary.enquiry.wonCount,
      value: scopedQuotations
        .filter((row) => (statusMaps.quotationStatusById.get(row.status) ?? '') === 'accepted')
        .reduce((sum, row) => sum + row.totalAmount, 0)
    }
  ];

  return steps.map((step, index) => ({
    ...step,
    value: round2(step.value),
    conversionRate:
      index === 0 || steps[index - 1].count === 0
        ? null
        : round2((step.count / steps[index - 1].count) * 100)
  }));
}

async function buildRevenueAnalytics(
  organizationId: string,
  filters: NormalizedFilters
) {
  const revenueFilters = {
    dateFrom: filters.dateFrom,
    dateTo: filters.dateTo,
    ...(filters.branch ? { branch: filters.branch } : {}),
    ...(filters.salesman ? { salesman: filters.salesman } : {}),
    ...(filters.productType ? { quotationType: filters.productType } : {})
  };

  const [
    endCustomers,
    billingCustomers,
    contractors,
    consultants,
    wonEnd,
    wonBilling,
    wonContractor,
    wonConsultant
  ] = await Promise.all([
    getRevenueByEndCustomer(organizationId, revenueFilters),
    getRevenueByBillingCustomer(organizationId, revenueFilters),
    getRevenueByContractor(organizationId, revenueFilters),
    getRevenueByConsultant(organizationId, revenueFilters),
    getRevenueByEndCustomer(organizationId, { ...revenueFilters, status: 'accepted' }),
    getRevenueByBillingCustomer(organizationId, { ...revenueFilters, status: 'accepted' }),
    getRevenueByContractor(organizationId, { ...revenueFilters, status: 'accepted' }),
    getRevenueByConsultant(organizationId, { ...revenueFilters, status: 'accepted' })
  ]);

  function withWonRevenue(
    rows: Awaited<ReturnType<typeof getRevenueByEndCustomer>>,
    wonRows: Awaited<ReturnType<typeof getRevenueByEndCustomer>>
  ): CrmDashboardRevenueRow[] {
    const wonMap = new Map(wonRows.map((row) => [row.customerId, row.revenue]));

    return rows.slice(0, 5).map((row) => ({
      customerId: row.customerId,
      customerCode: row.customerCode,
      customerName: row.customerName,
      roleCode: row.roleCode,
      revenue: row.revenue,
      quotationCount: row.quotationCount,
      wonRevenue: wonMap.get(row.customerId) ?? 0
    }));
  }

  return {
    topEndCustomers: withWonRevenue(endCustomers, wonEnd),
    topBillingCustomers: withWonRevenue(billingCustomers, wonBilling),
    topContractors: withWonRevenue(contractors, wonContractor),
    topConsultants: withWonRevenue(consultants, wonConsultant)
  };
}

async function buildSalesRanking(
  scopedEnquiries: Array<typeof crmEnquiries.$inferSelect>,
  scopedQuotations: Array<typeof crmQuotations.$inferSelect>,
  statusMaps: StatusMaps
): Promise<CrmDashboardSalesRankingRow[]> {
  const userIds = [
    ...new Set(
      [
        ...scopedEnquiries.map((row) => row.assignedToUserId).filter(Boolean),
        ...scopedQuotations.map((row) => row.salesmanId).filter(Boolean)
      ] as string[]
    )
  ];
  const salesUsers = userIds.length
    ? await db.select({ id: users.id, name: users.name }).from(users).where(inArray(users.id, userIds))
    : [];
  const userMap = new Map(salesUsers.map((row) => [row.id, row.name]));
  const rankingMap = new Map<string, CrmDashboardSalesRankingRow>();

  for (const row of scopedEnquiries) {
    if (!row.assignedToUserId) continue;
    const current =
      rankingMap.get(row.assignedToUserId) ??
      {
        salesPersonId: row.assignedToUserId,
        salesPersonName: userMap.get(row.assignedToUserId) ?? 'Unknown',
        leadCount: 0,
        enquiryCount: 0,
        quotationCount: 0,
        approvedQuotations: 0,
        wonRevenue: 0,
        conversionRate: 0
      };

    if (isPipelineStage(row, 'lead')) {
      current.leadCount += 1;
    }

    if (isPipelineStage(row, 'enquiry')) {
      current.enquiryCount += 1;
    }

    rankingMap.set(row.assignedToUserId, current);
  }

  for (const row of scopedQuotations) {
    if (!row.salesmanId) continue;
    const current =
      rankingMap.get(row.salesmanId) ??
      {
        salesPersonId: row.salesmanId,
        salesPersonName: userMap.get(row.salesmanId) ?? 'Unknown',
        leadCount: 0,
        enquiryCount: 0,
        quotationCount: 0,
        approvedQuotations: 0,
        wonRevenue: 0,
        conversionRate: 0
      };
    const statusCode = statusMaps.quotationStatusById.get(row.status) ?? '';

    if (statusCode !== 'cancelled') {
      current.quotationCount += 1;
    }

    if (statusCode === 'approved') {
      current.approvedQuotations += 1;
    }

    if (statusCode === 'accepted') {
      current.wonRevenue += row.totalAmount;
    }

    rankingMap.set(row.salesmanId, current);
  }

  return [...rankingMap.values()]
    .map((row) => ({
      ...row,
      wonRevenue: round2(row.wonRevenue),
      conversionRate:
        row.quotationCount > 0 ? round2((row.approvedQuotations / row.quotationCount) * 100) : 0
    }))
    .sort((a, b) => {
      if (b.wonRevenue !== a.wonRevenue) return b.wonRevenue - a.wonRevenue;
      if (b.quotationCount !== a.quotationCount) return b.quotationCount - a.quotationCount;
      return a.salesPersonName.localeCompare(b.salesPersonName);
    })
    .slice(0, 10);
}

async function buildFollowups(
  context: DashboardContext,
  filters: NormalizedFilters
): Promise<{ summary: CrmDashboardFollowupSummary; upcoming: CrmDashboardFollowupRow[] }> {
  const allowQuotationFollowups = canViewCommercialData(context);
  const [enquiryFollowups, quotationFollowups] = await Promise.all([
    db
      .select()
      .from(crmEnquiryFollowups)
      .where(
        and(
          eq(crmEnquiryFollowups.organizationId, context.organizationId),
          isNull(crmEnquiryFollowups.deletedAt)
        )
      )
      .orderBy(asc(crmEnquiryFollowups.followupDate)),
    allowQuotationFollowups
      ? db
          .select()
          .from(crmQuotationFollowups)
          .where(
            and(
              eq(crmQuotationFollowups.organizationId, context.organizationId),
              isNull(crmQuotationFollowups.deletedAt)
            )
          )
          .orderBy(asc(crmQuotationFollowups.followupDate))
      : []
  ]);

  const { from, to } = toDayBounds(filters);
  const now = new Date();
  const weekStart = startOfWeek(now, { weekStartsOn: 1 });
  const weekEnd = endOfWeek(now, { weekStartsOn: 1 });
  const enquiryIds = [...new Set(enquiryFollowups.map((row) => row.enquiryId))];
  const quotationIds = [...new Set(quotationFollowups.map((row) => row.quotationId))];
  const [enquiries, quotations] = await Promise.all([
    enquiryIds.length ? db.select().from(crmEnquiries).where(inArray(crmEnquiries.id, enquiryIds)) : [],
    quotationIds.length ? db.select().from(crmQuotations).where(inArray(crmQuotations.id, quotationIds)) : []
  ]);
  const customerIds = [...new Set([...enquiries.map((row) => row.customerId), ...quotations.map((row) => row.customerId)])];
  const salesIds = [
    ...new Set(
      [...enquiries.map((row) => row.assignedToUserId).filter(Boolean), ...quotations.map((row) => row.salesmanId).filter(Boolean)] as string[]
    )
  ];
  const [customers, salesUsers] = await Promise.all([
    customerIds.length ? db.select().from(crmCustomers).where(inArray(crmCustomers.id, customerIds)) : [],
    salesIds.length ? db.select({ id: users.id, name: users.name }).from(users).where(inArray(users.id, salesIds)) : []
  ]);
  const enquiryMap = new Map(enquiries.map((row) => [row.id, row]));
  const quotationMap = new Map(quotations.map((row) => [row.id, row]));
  const customerMap = new Map(customers.map((row) => [row.id, row.name]));
  const salesMap = new Map(salesUsers.map((row) => [row.id, row.name]));

  const enquiryRows = enquiryFollowups
    .filter((row) => {
      const enquiry = enquiryMap.get(row.enquiryId);
      return enquiry ? canAccessDashboardEnquiry(enquiry, context) : false;
    })
    .filter((row) => !isBefore(row.followupDate, from) && !isAfter(row.followupDate, to))
    .flatMap<CrmDashboardFollowupRow>((row) => {
      const enquiry = enquiryMap.get(row.enquiryId);

      if (!enquiry) {
        return [];
      }

      return [
        {
          id: row.id,
          sourceType: 'enquiry',
          followupDate: row.followupDate.toISOString(),
          customerName: customerMap.get(enquiry.customerId) ?? 'Unknown customer',
          enquiryName: enquiry.projectName ?? enquiry.title,
          assignedSalesName: enquiry.assignedToUserId
            ? (salesMap.get(enquiry.assignedToUserId) ?? null)
            : null,
          outcome: row.outcome
        }
      ];
    });
  const quotationRows = quotationFollowups
    .filter((row) => {
      const quotation = quotationMap.get(row.quotationId);
      return quotation ? canAccessDashboardQuotation(quotation, context) : false;
    })
    .filter((row) => !isBefore(row.followupDate, from) && !isAfter(row.followupDate, to))
    .flatMap<CrmDashboardFollowupRow>((row) => {
      const quotation = quotationMap.get(row.quotationId);

      if (!quotation) {
        return [];
      }

      return [
        {
          id: row.id,
          sourceType: 'quotation',
          followupDate: row.followupDate.toISOString(),
          customerName: customerMap.get(quotation.customerId) ?? 'Unknown customer',
          enquiryName: quotation.projectName ?? quotation.code,
          assignedSalesName: quotation.salesmanId
            ? (salesMap.get(quotation.salesmanId) ?? null)
            : null,
          outcome: row.outcome
        }
      ];
    });
  const rows: CrmDashboardFollowupRow[] = [...enquiryRows, ...quotationRows];

  return {
    summary: {
      dueToday: rows.filter((row) => isSameDay(new Date(row.followupDate), now)).length,
      dueThisWeek: rows.filter((row) => {
        const date = new Date(row.followupDate);
        return !isBefore(date, weekStart) && !isAfter(date, weekEnd);
      }).length,
      overdue: rows.filter((row) => isBefore(new Date(row.followupDate), startOfDay(now)) && !row.outcome).length,
      completed: rows.filter((row) => !!row.outcome).length
    },
    upcoming: rows
      .filter((row) => !isBefore(new Date(row.followupDate), startOfDay(now)))
      .sort((a, b) => new Date(a.followupDate).getTime() - new Date(b.followupDate).getTime())
      .slice(0, 10)
  };
}

async function buildApprovalAnalytics(
  context: DashboardContext
): Promise<{ summary: CrmDashboardApprovalSummary; myPendingApprovals: CrmDashboardApprovalRow[] }> {
  const pending = await listApprovalRequests(context.organizationId, {
    page: 1,
    limit: 50,
    status: 'pending'
  });
  const todayStart = startOfDay(new Date());
  const todayEnd = endOfDay(new Date());
  const rows = await db
    .select()
    .from(crmApprovalRequests)
    .where(and(eq(crmApprovalRequests.organizationId, context.organizationId), isNull(crmApprovalRequests.deletedAt)))
    .orderBy(desc(crmApprovalRequests.updatedAt));

  const approvedRows = rows.filter(
    (row) => row.status === 'approved' && row.completedAt && !isBefore(row.completedAt, todayStart) && !isAfter(row.completedAt, todayEnd)
  );
  const rejectedRows = rows.filter(
    (row) => row.status === 'rejected' && row.completedAt && !isBefore(row.completedAt, todayStart) && !isAfter(row.completedAt, todayEnd)
  );
  const returnedRows = rows.filter(
    (row) => row.status === 'returned' && row.completedAt && !isBefore(row.completedAt, todayStart) && !isAfter(row.completedAt, todayEnd)
  );
  const approvedDurations = approvedRows.map((row) => (row.completedAt!.getTime() - row.requestedAt.getTime()) / (1000 * 60 * 60));

  return {
    summary: {
      pendingApprovals: pending.totalItems,
      approvedToday: approvedRows.length,
      rejectedToday: rejectedRows.length,
      returnedToday: returnedRows.length,
      averageApprovalTimeHours: average(approvedDurations)
    },
    myPendingApprovals: pending.items
      .filter((item) => item.currentStepRoleCode === context.businessRole || context.membershipRole === 'admin')
      .slice(0, 10)
      .map((item) => ({
        id: item.id,
        entityCode: item.entityCode,
        entityTitle: item.entityTitle,
        workflowName: item.workflowName,
        currentStepRoleName: item.currentStepRoleName,
        requestedAt: item.requestedAt
      }))
  };
}

async function buildHotProjects(
  context: DashboardContext,
  filters: NormalizedFilters,
  statusMaps: StatusMaps
): Promise<CrmDashboardHotProjectRow[]> {
  const rows = await db
    .select()
    .from(crmEnquiries)
    .where(
      and(
        eq(crmEnquiries.organizationId, context.organizationId),
        isNull(crmEnquiries.deletedAt),
        eq(crmEnquiries.isHotProject, true)
      )
    )
    .orderBy(desc(crmEnquiries.updatedAt));
  const scoped = rows
    .filter((row) => canAccessDashboardEnquiry(row, context))
    .filter((row) => matchesEnquiryFilters(row, filters))
    .filter((row) => isPipelineStage(row, 'enquiry'))
    .slice(0, 10);
  const customerIds = [...new Set(scoped.map((row) => row.customerId))];
  const salesIds = [...new Set(scoped.map((row) => row.assignedToUserId).filter(Boolean))] as string[];
  const [customers, salesUsers] = await Promise.all([
    customerIds.length ? db.select().from(crmCustomers).where(inArray(crmCustomers.id, customerIds)) : [],
    salesIds.length ? db.select({ id: users.id, name: users.name }).from(users).where(inArray(users.id, salesIds)) : []
  ]);
  const customerMap = new Map(customers.map((row) => [row.id, row.name]));
  const salesMap = new Map(salesUsers.map((row) => [row.id, row.name]));

  return scoped.map((row) => ({
    enquiryId: row.id,
    enquiryCode: row.code,
    projectName: row.projectName ?? row.title,
    customerName: customerMap.get(row.customerId) ?? 'Unknown customer',
    value: row.estimatedValue ?? 0,
    assignedSalesName: row.assignedToUserId ? (salesMap.get(row.assignedToUserId) ?? null) : null,
    probability: row.chancePercent
  }));
}

export async function getCrmDashboardData(
  context: DashboardContext,
  rawFilters: CrmDashboardFilters
): Promise<CrmDashboardResponse> {
  const filters = normalizeDashboardFilters(rawFilters);
  const allowCommercialData = canViewCommercialData(context);
  const allowApprovalData = canViewApprovalData(context);
  const [referenceData, statusMaps, scoped] = await Promise.all([
    getReferenceData(context.organizationId),
    getStatusMaps(context.organizationId),
    loadScopedRows(context, filters)
  ]);
  const summary = buildSummary(scoped.enquiries, scoped.quotations, statusMaps);
  const [revenueAnalytics, salesRanking, followups, approvals, hotProjects] = await Promise.all([
    allowCommercialData
      ? buildRevenueAnalytics(context.organizationId, filters)
      : Promise.resolve({
          topEndCustomers: [],
          topBillingCustomers: [],
          topContractors: [],
          topConsultants: []
        }),
    allowCommercialData
      ? buildSalesRanking(scoped.enquiries, scoped.quotations, statusMaps)
      : Promise.resolve([]),
    buildFollowups(context, filters),
    allowApprovalData
      ? buildApprovalAnalytics(context)
      : Promise.resolve({
          summary: {
            pendingApprovals: 0,
            approvedToday: 0,
            rejectedToday: 0,
            returnedToday: 0,
            averageApprovalTimeHours: 0
          },
          myPendingApprovals: []
        }),
    allowCommercialData
      ? buildHotProjects(context, filters, statusMaps)
      : Promise.resolve([])
  ]);

  return {
    success: true,
    time: new Date().toISOString(),
    message: 'CRM dashboard loaded successfully',
    filters,
    referenceData,
    summary,
    funnel: buildFunnel(summary, scoped.quotations, statusMaps),
    revenueAnalytics,
    salesRanking,
    followups,
    approvals,
    hotProjects,
    visibility: {
      canViewCommercialData: allowCommercialData,
      canViewApprovalData: allowApprovalData
    }
  };
}
