import { auditAction } from '@/features/foundation/audit-log/service';
import { canViewQuotationPricing } from '@/lib/auth/crm-access';
import type {
  CrmReportCatalogResponse,
  CrmReportFiltersResponse,
  CrmReportsResponse,
  CrmSharedReportFilters,
  CrmPipelineReportResponse,
  CrmLeadAgingReportResponse,
  CrmOpportunityAgingReportResponse
} from '../api/types';
import { buildReportCatalogGroups } from './builders/catalog-builder';
import { resolveCrmAccess } from './context';
import { listReportRegistry } from './datasets/catalog';
import { listPipelineReportDataset } from './datasets/pipeline-report.dataset';
import { buildSharedReportFilters } from './filters/shared';
import type { ParsedCrmReportFilters, PipelineDatasetRecord, ResolvedReportContext } from './types';

export function buildResolvedReportContext(input: Parameters<typeof resolveCrmAccess>[0]) {
  return resolveCrmAccess(input);
}

function getDayDifference(start: Date, end: Date) {
  const milliseconds = end.getTime() - start.getTime();
  return Math.max(0, Math.floor(milliseconds / 86_400_000));
}

function toPercent(numerator: number, denominator: number) {
  if (denominator <= 0) {
    return 0;
  }

  return Number(((numerator / denominator) * 100).toFixed(2));
}

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

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

function sumValues(items: PipelineDatasetRecord[], selector: (row: PipelineDatasetRecord) => number) {
  return items.reduce((sum, row) => sum + selector(row), 0);
}

function isConvertedLead(row: PipelineDatasetRecord) {
  return row.pipelineStage !== 'lead' || Boolean(row.assignedToUserId);
}

function isOpenOpportunity(row: PipelineDatasetRecord) {
  return row.pipelineStage === 'opportunity';
}

function buildLeadPipelineRows(rows: PipelineDatasetRecord[]) {
  const grouped = new Map<string, CrmPipelineReportResponse['leadPipeline'][number]>();

  for (const row of rows) {
    const key = [
      row.leadSourceLabel,
      row.customerOwnerName,
      row.createdByName,
      row.branchLabel,
      row.productTypeLabel
    ].join('|');
    const current =
      grouped.get(key) ??
      {
        leadSourceLabel: row.leadSourceLabel,
        customerOwnerName: row.customerOwnerName,
        createdByName: row.createdByName,
        branchLabel: row.branchLabel,
        productTypeLabel: row.productTypeLabel,
        totalLeads: 0,
        newLeads: 0,
        assignedLeads: 0,
        unassignedLeads: 0,
        convertedLeads: 0
      };

    current.totalLeads += 1;
    current.newLeads += 1;

    if (row.assignedToUserId) {
      current.assignedLeads += 1;
    } else {
      current.unassignedLeads += 1;
    }

    if (isConvertedLead(row)) {
      current.convertedLeads += 1;
    }

    grouped.set(key, current);
  }

  return [...grouped.values()].toSorted((left, right) => right.totalLeads - left.totalLeads);
}

function buildOpportunityPipelineRows(rows: PipelineDatasetRecord[]) {
  const grouped = new Map<string, CrmPipelineReportResponse['opportunityPipeline'][number]>();

  for (const row of rows) {
    const key = [
      row.assignedToName ?? 'Unassigned',
      row.branchLabel,
      row.productTypeLabel,
      row.customerOwnerName
    ].join('|');
    const current =
      grouped.get(key) ??
      {
        salesmanName: row.assignedToName ?? 'Unassigned',
        branchLabel: row.branchLabel,
        productTypeLabel: row.productTypeLabel,
        customerOwnerName: row.customerOwnerName,
        openOpportunities: 0,
        convertedToQuotation: 0,
        won: 0,
        lost: 0
      };

    if (isOpenOpportunity(row)) {
      current.openOpportunities += 1;
    }

    if (row.quotationCount > 0) {
      current.convertedToQuotation += 1;
    }

    if (row.pipelineStage === 'closed_won') {
      current.won += 1;
    }

    if (row.pipelineStage === 'closed_lost') {
      current.lost += 1;
    }

    grouped.set(key, current);
  }

  return [...grouped.values()].toSorted(
    (left, right) =>
      right.openOpportunities + right.convertedToQuotation + right.won + right.lost -
      (left.openOpportunities + left.convertedToQuotation + left.won + left.lost)
  );
}

function buildLeadConversionRows(rows: PipelineDatasetRecord[]) {
  const grouped = new Map<string, CrmPipelineReportResponse['leadConversion'][number]>();

  for (const row of rows) {
    const key = [
      row.leadSourceLabel,
      row.branchLabel,
      row.productTypeLabel,
      row.createdByName
    ].join('|');
    const current =
      grouped.get(key) ??
      {
        leadSourceLabel: row.leadSourceLabel,
        branchLabel: row.branchLabel,
        productTypeLabel: row.productTypeLabel,
        marketingUserName: row.createdByName,
        totalLeads: 0,
        convertedOpportunities: 0,
        conversionRate: 0
      };

    current.totalLeads += 1;
    if (isConvertedLead(row)) {
      current.convertedOpportunities += 1;
    }

    grouped.set(key, current);
  }

  return [...grouped.values()]
    .map((row) => ({
      ...row,
      conversionRate: toPercent(row.convertedOpportunities, row.totalLeads)
    }))
    .toSorted((left, right) => right.totalLeads - left.totalLeads);
}

function buildOpportunityConversionRows(rows: PipelineDatasetRecord[]) {
  const opportunityRows = rows.filter((row) => row.pipelineStage !== 'lead' || row.assignedToUserId);
  const grouped = new Map<string, CrmPipelineReportResponse['opportunityConversion'][number]>();

  for (const row of opportunityRows) {
    const key = [(row.assignedToName ?? 'Unassigned'), row.branchLabel, row.productTypeLabel].join('|');
    const current =
      grouped.get(key) ??
      {
        salesmanName: row.assignedToName ?? 'Unassigned',
        branchLabel: row.branchLabel,
        productTypeLabel: row.productTypeLabel,
        totalOpportunities: 0,
        opportunitiesWithQuotation: 0,
        conversionRate: 0
      };

    current.totalOpportunities += 1;
    if (row.quotationCount > 0) {
      current.opportunitiesWithQuotation += 1;
    }

    grouped.set(key, current);
  }

  return [...grouped.values()]
    .map((row) => ({
      ...row,
      conversionRate: toPercent(row.opportunitiesWithQuotation, row.totalOpportunities)
    }))
    .toSorted((left, right) => right.totalOpportunities - left.totalOpportunities);
}

function buildFunnelRows(rows: PipelineDatasetRecord[], canViewRevenueValues: boolean) {
  const leadCount = rows.length;
  const opportunityRows = rows.filter((row) => isConvertedLead(row));
  const quotationRows = rows.filter((row) => row.quotationCount > 0);
  const wonRows = rows.filter((row) => row.pipelineStage === 'closed_won');
  const lostRows = rows.filter((row) => row.pipelineStage === 'closed_lost');

  const valueOrNull = (value: number) => (canViewRevenueValues ? value : null);

  return [
    {
      stageKey: 'lead' as const,
      stageLabel: 'Lead',
      count: leadCount,
      value: valueOrNull(sumValues(rows, (row) => row.estimatedValue)),
      conversionRate: null
    },
    {
      stageKey: 'opportunity' as const,
      stageLabel: 'Opportunity',
      count: opportunityRows.length,
      value: valueOrNull(sumValues(opportunityRows, (row) => row.estimatedValue)),
      conversionRate: toPercent(opportunityRows.length, leadCount)
    },
    {
      stageKey: 'quotation' as const,
      stageLabel: 'Quotation',
      count: quotationRows.length,
      value: valueOrNull(sumValues(quotationRows, (row) => row.quotationValue)),
      conversionRate: toPercent(quotationRows.length, opportunityRows.length)
    },
    {
      stageKey: 'closed_won' as const,
      stageLabel: 'Closed Won',
      count: wonRows.length,
      value: valueOrNull(sumValues(wonRows, (row) => row.poAmount || row.quotationValue || row.estimatedValue)),
      conversionRate: toPercent(wonRows.length, quotationRows.length)
    },
    {
      stageKey: 'closed_lost' as const,
      stageLabel: 'Closed Lost',
      count: lostRows.length,
      value: valueOrNull(sumValues(lostRows, (row) => row.estimatedValue || row.quotationValue)),
      conversionRate: toPercent(lostRows.length, quotationRows.length)
    }
  ];
}

function buildLeadAging(rows: PipelineDatasetRecord[], filters: ParsedCrmReportFilters): CrmLeadAgingReportResponse {
  const now = new Date();
  const leadRows = rows
    .filter((row) => row.pipelineStage === 'lead')
    .map((row) => ({
      leadId: row.opportunityId,
      leadCode: row.opportunityCode,
      customerName: row.customerName,
      assignedUserName: row.assignedToName,
      createdDate: row.createdAt.toISOString(),
      agingDays: getDayDifference(row.createdAt, now)
    }))
    .toSorted((left, right) => right.agingDays - left.agingDays);
  const ages = leadRows.map((row) => row.agingDays);

  return {
    success: true,
    time: new Date().toISOString(),
    message: 'CRM lead aging report loaded successfully',
    filters,
    summary: {
      leadCount: leadRows.length,
      averageAgingDays: average(ages),
      maximumAgingDays: ages.length ? Math.max(...ages) : 0
    },
    buckets: [
      { key: '0_7', label: '0-7 Days', count: leadRows.filter((row) => row.agingDays <= 7).length },
      { key: '8_14', label: '8-14 Days', count: leadRows.filter((row) => row.agingDays >= 8 && row.agingDays <= 14).length },
      { key: '15_30', label: '15-30 Days', count: leadRows.filter((row) => row.agingDays >= 15 && row.agingDays <= 30).length },
      { key: '31_60', label: '31-60 Days', count: leadRows.filter((row) => row.agingDays >= 31 && row.agingDays <= 60).length },
      { key: '60_plus', label: '60+ Days', count: leadRows.filter((row) => row.agingDays > 60).length }
    ],
    rows: leadRows
  };
}

function buildOpportunityAging(
  rows: PipelineDatasetRecord[],
  filters: ParsedCrmReportFilters
): CrmOpportunityAgingReportResponse {
  const now = new Date();
  const opportunityRows = rows
    .filter((row) => row.pipelineStage === 'opportunity')
    .map((row) => ({
      opportunityId: row.opportunityId,
      opportunityCode: row.opportunityCode,
      customerName: row.customerName,
      salesmanName: row.assignedToName,
      lastFollowupDate: row.lastFollowupDate?.toISOString() ?? null,
      agingDays: getDayDifference(row.createdAt, now)
    }))
    .toSorted((left, right) => right.agingDays - left.agingDays);
  const ages = opportunityRows.map((row) => row.agingDays);

  return {
    success: true,
    time: new Date().toISOString(),
    message: 'CRM opportunity aging report loaded successfully',
    filters,
    summary: {
      openOpportunities: opportunityRows.length,
      averageAgingDays: average(ages),
      maximumAgingDays: ages.length ? Math.max(...ages) : 0
    },
    buckets: [
      { key: '0_14', label: '0-14 Days', count: opportunityRows.filter((row) => row.agingDays <= 14).length },
      { key: '15_30', label: '15-30 Days', count: opportunityRows.filter((row) => row.agingDays >= 15 && row.agingDays <= 30).length },
      { key: '31_60', label: '31-60 Days', count: opportunityRows.filter((row) => row.agingDays >= 31 && row.agingDays <= 60).length },
      { key: '60_plus', label: '60+ Days', count: opportunityRows.filter((row) => row.agingDays > 60).length }
    ],
    rows: opportunityRows
  };
}

async function auditReportView(context: ResolvedReportContext, reportCode: string, filters?: Record<string, unknown>, recordCount?: number) {
  await auditAction({
    organizationId: context.organizationId,
    userId: context.userId,
    entityType: 'crm_report',
    entityId: reportCode,
    action: 'view_pipeline_report',
    afterData: {
      reportCode,
      filters: filters ?? {},
      userId: context.userId,
      recordCount: recordCount ?? 0
    }
  });
}

export async function getCrmReportsResponse(
  context: ResolvedReportContext
): Promise<CrmReportsResponse> {
  const items = await listReportRegistry(context);
  const groups = buildReportCatalogGroups(items);

  await auditReportView(context, 'report_center');

  return {
    success: true,
    time: new Date().toISOString(),
    message: 'CRM reports loaded successfully',
    items,
    groups
  };
}

export async function getCrmReportCatalogResponse(
  context: ResolvedReportContext
): Promise<CrmReportCatalogResponse> {
  const items = await listReportRegistry(context);
  const groups = buildReportCatalogGroups(items);

  await auditReportView(context, 'report_catalog');

  return {
    success: true,
    time: new Date().toISOString(),
    message: 'CRM report catalog loaded successfully',
    groups
  };
}

export async function getCrmReportFiltersResponse(
  context: ResolvedReportContext
): Promise<CrmReportFiltersResponse> {
  const filters = await buildSharedReportFilters(context);

  await auditReportView(context, 'report_filters');

  return {
    success: true,
    time: new Date().toISOString(),
    message: 'CRM report filters loaded successfully',
    filters
  };
}

export async function getCrmPipelineReportResponse(
  context: ResolvedReportContext,
  filters: CrmSharedReportFilters
): Promise<CrmPipelineReportResponse> {
  const dataset = await listPipelineReportDataset(context, filters);
  const canViewRevenueValues = canViewQuotationPricing(context);
  const response: CrmPipelineReportResponse = {
    success: true,
    time: new Date().toISOString(),
    message: 'CRM pipeline report loaded successfully',
    filters: dataset.filters,
    summary: {
      totalLeads: dataset.rows.length,
      openOpportunities: dataset.rows.filter((row) => row.pipelineStage === 'opportunity').length,
      quotations: dataset.rows.filter((row) => row.quotationCount > 0).length,
      won: dataset.rows.filter((row) => row.pipelineStage === 'closed_won').length,
      lost: dataset.rows.filter((row) => row.pipelineStage === 'closed_lost').length,
      canViewRevenueValues
    },
    leadPipeline: buildLeadPipelineRows(dataset.rows),
    opportunityPipeline: buildOpportunityPipelineRows(dataset.rows),
    leadConversion: buildLeadConversionRows(dataset.rows),
    opportunityConversion: buildOpportunityConversionRows(dataset.rows),
    funnel: buildFunnelRows(dataset.rows, canViewRevenueValues)
  };

  await auditReportView(context, 'pipeline_suite', { ...dataset.filters }, dataset.rows.length);

  return response;
}

export async function getCrmLeadAgingReportResponse(
  context: ResolvedReportContext,
  filters: CrmSharedReportFilters
): Promise<CrmLeadAgingReportResponse> {
  const dataset = await listPipelineReportDataset(context, filters);
  const response = buildLeadAging(dataset.rows, dataset.filters);

  await auditReportView(context, 'lead_aging', { ...dataset.filters }, response.rows.length);

  return response;
}

export async function getCrmOpportunityAgingReportResponse(
  context: ResolvedReportContext,
  filters: CrmSharedReportFilters
): Promise<CrmOpportunityAgingReportResponse> {
  const dataset = await listPipelineReportDataset(context, filters);
  const response = buildOpportunityAging(dataset.rows, dataset.filters);

  await auditReportView(context, 'opportunity_aging', { ...dataset.filters }, response.rows.length);

  return response;
}

