import {
  and,
  asc,
  eq,
  gte,
  ilike,
  inArray,
  isNull,
  lte,
  ne,
  or,
  sql,
  type SQL
} from 'drizzle-orm';
import {
  crmCustomers,
  crmEnquiryCustomers,
  crmQuotationCustomers,
  crmQuotations
} from '@/db/schema';
import { db } from '@/lib/db';
import { getActiveOptionsByCategory } from '@/features/foundation/master-options/service';
import {
  PROJECT_PARTY_OPTION_CATEGORY,
  resolveProjectPartyRole
} from '@/features/crm/shared/project-party';
import type { RevenueAttributionFilters, RevenueAttributionSummary } from './types';

type SupportedRevenueRole = 'end_customer' | 'billing_customer' | 'contractor' | 'consultant';

interface NormalizedProjectParty {
  customerId: string;
  roleCode: string;
}

type ProjectPartyTableAvailability = {
  enquiryCustomers: boolean;
  quotationCustomers: boolean;
};

let projectPartyTableAvailability: ProjectPartyTableAvailability | null = null;

function splitFilterValue(value?: string) {
  return value
    ?.split(',')
    .map((item) => item.trim())
    .filter(Boolean) ?? [];
}

function buildRevenueAttributionFilters(
  organizationId: string,
  filters: RevenueAttributionFilters
): SQL[] {
  const statuses = splitFilterValue(filters.status);
  const quotationTypes = splitFilterValue(filters.quotationType);
  const branches = splitFilterValue(filters.branch);
  const customers = splitFilterValue(filters.customer);
  const enquiries = splitFilterValue(filters.enquiry);

  return [
    eq(crmQuotations.organizationId, organizationId),
    isNull(crmQuotations.deletedAt),
    ...(statuses.length
      ? [inArray(crmQuotations.status, statuses)]
      : filters.includeCancelled
        ? []
        : [ne(crmQuotations.status, 'cancelled')]),
    ...(quotationTypes.length ? [inArray(crmQuotations.quotationType, quotationTypes)] : []),
    ...(branches.length ? [inArray(crmQuotations.branchId, branches)] : []),
    ...(customers.length ? [inArray(crmQuotations.customerId, customers)] : []),
    ...(enquiries.length ? [inArray(crmQuotations.enquiryId, enquiries)] : []),
    ...(filters.hot === 'true' ? [eq(crmQuotations.isHotProject, true)] : []),
    ...(filters.hot === 'false' ? [eq(crmQuotations.isHotProject, false)] : []),
    ...(filters.quotationDateFrom ? [gte(crmQuotations.quotationDate, new Date(filters.quotationDateFrom))] : []),
    ...(filters.quotationDateTo ? [lte(crmQuotations.quotationDate, new Date(filters.quotationDateTo))] : []),
    ...(filters.search
      ? [
          or(
            ilike(crmQuotations.code, `%${filters.search}%`),
            ilike(crmQuotations.projectName, `%${filters.search}%`),
            ilike(crmQuotations.projectLocation, `%${filters.search}%`),
            ilike(crmQuotations.reference, `%${filters.search}%`),
            ilike(crmQuotations.competitor, `%${filters.search}%`)
          )!
        ]
      : [])
  ];
}

function normalizeParties(
  rows: Array<{ customerId: string; role: string }>,
  roleOptions: Awaited<ReturnType<typeof getActiveOptionsByCategory>>
) {
  const normalized: NormalizedProjectParty[] = [];
  const seen = new Set<string>();

  for (const row of rows) {
    const resolvedRole = resolveProjectPartyRole(roleOptions, row.role);

    if (!resolvedRole) {
      continue;
    }

    const key = `${row.customerId}::${resolvedRole.code}`;

    if (seen.has(key)) {
      continue;
    }

    seen.add(key);
    normalized.push({
      customerId: row.customerId,
      roleCode: resolvedRole.code
    });
  }

  return normalized;
}

function groupNormalizedParties<TGroupKey extends string>(
  rows: Array<{ groupKey: TGroupKey; customerId: string; role: string }>,
  roleOptions: Awaited<ReturnType<typeof getActiveOptionsByCategory>>
) {
  const grouped = new Map<TGroupKey, Array<{ customerId: string; role: string }>>();

  for (const row of rows) {
    const existing = grouped.get(row.groupKey) ?? [];
    existing.push({
      customerId: row.customerId,
      role: row.role
    });
    grouped.set(row.groupKey, existing);
  }

  return new Map(
    [...grouped.entries()].map(([groupKey, groupRows]) => [
      groupKey,
      normalizeParties(groupRows, roleOptions)
    ])
  );
}

function pickAttributedParties(
  roleCode: SupportedRevenueRole,
  quotationParties: NormalizedProjectParty[],
  enquiryParties: NormalizedProjectParty[]
) {
  const authoritativeParties = quotationParties.length > 0 ? quotationParties : enquiryParties;

  if (roleCode === 'end_customer') {
    const endCustomers = authoritativeParties.filter((party) => party.roleCode === 'end_customer');

    if (endCustomers.length > 0) {
      return endCustomers;
    }

    return authoritativeParties.filter((party) => party.roleCode === 'billing_customer');
  }

  return authoritativeParties.filter((party) => party.roleCode === roleCode);
}

async function getProjectPartyTableAvailability(): Promise<ProjectPartyTableAvailability> {
  if (projectPartyTableAvailability) {
    return projectPartyTableAvailability;
  }

  const rows = await db.execute<{ tableName: string }>(sql`
    select table_name as "tableName"
    from information_schema.tables
    where table_schema = 'public'
      and table_name in ('crm_enquiry_customers', 'crm_quotation_customers')
  `);

  const available = new Set(rows.map((row) => row.tableName));

  projectPartyTableAvailability = {
    enquiryCustomers: available.has('crm_enquiry_customers'),
    quotationCustomers: available.has('crm_quotation_customers')
  };

  return projectPartyTableAvailability;
}

async function getRevenueByRole(
  organizationId: string,
  roleCode: SupportedRevenueRole,
  filters: RevenueAttributionFilters = {}
): Promise<RevenueAttributionSummary[]> {
  const tableAvailability = await getProjectPartyTableAvailability();
  const whereFilters = buildRevenueAttributionFilters(organizationId, filters);
  const where = whereFilters.length === 1 ? whereFilters[0] : and(...whereFilters);

  const quotations = await db
    .select({
      id: crmQuotations.id,
      enquiryId: crmQuotations.enquiryId,
      customerId: crmQuotations.customerId,
      totalAmount: crmQuotations.totalAmount
    })
    .from(crmQuotations)
    .where(where)
    .orderBy(asc(crmQuotations.quotationDate), asc(crmQuotations.code));

  if (quotations.length === 0) {
    return [];
  }

  const quotationIds = quotations.map((quotation) => quotation.id);
  const enquiryIds = [...new Set(quotations.map((quotation) => quotation.enquiryId).filter(Boolean))] as string[];

  const [roleOptions, quotationParties, enquiryParties] = await Promise.all([
    getActiveOptionsByCategory(PROJECT_PARTY_OPTION_CATEGORY, { organizationId }),
    tableAvailability.quotationCustomers
      ? db
          .select({
            quotationId: crmQuotationCustomers.quotationId,
            customerId: crmQuotationCustomers.customerId,
            role: crmQuotationCustomers.role
          })
          .from(crmQuotationCustomers)
          .where(
            and(
              eq(crmQuotationCustomers.organizationId, organizationId),
              inArray(crmQuotationCustomers.quotationId, quotationIds),
              isNull(crmQuotationCustomers.deletedAt)
            )
          )
      : [],
    tableAvailability.enquiryCustomers && enquiryIds.length
      ? db
          .select({
            enquiryId: crmEnquiryCustomers.enquiryId,
            customerId: crmEnquiryCustomers.customerId,
            role: crmEnquiryCustomers.role
          })
          .from(crmEnquiryCustomers)
          .where(
            and(
              eq(crmEnquiryCustomers.organizationId, organizationId),
              inArray(crmEnquiryCustomers.enquiryId, enquiryIds),
              isNull(crmEnquiryCustomers.deletedAt)
            )
          )
      : []
  ]);

  const quotationPartyMap = groupNormalizedParties(
    quotationParties.map((row) => ({
      groupKey: row.quotationId,
      customerId: row.customerId,
      role: row.role
    })),
    roleOptions
  );

  const enquiryPartyMap = groupNormalizedParties(
    enquiryParties.map((row) => ({
      groupKey: row.enquiryId,
      customerId: row.customerId,
      role: row.role
    })),
    roleOptions
  );

  const attributedCustomerIds = new Set<string>();
  const attributionByQuotation = new Map<string, NormalizedProjectParty[]>();

  for (const quotation of quotations) {
    const quotationAttributedParties = quotationPartyMap.get(quotation.id) ?? [];
    const enquiryAttributedParties = quotation.enquiryId
      ? (enquiryPartyMap.get(quotation.enquiryId) ?? [])
      : [];
    const attributedParties =
      quotationAttributedParties.length > 0 || enquiryAttributedParties.length > 0
        ? pickAttributedParties(roleCode, quotationAttributedParties, enquiryAttributedParties)
        : roleCode === 'end_customer' || roleCode === 'billing_customer'
          ? [{ customerId: quotation.customerId, roleCode }]
          : [];

    attributionByQuotation.set(quotation.id, attributedParties);

    for (const party of attributedParties) {
      attributedCustomerIds.add(party.customerId);
    }
  }

  if (attributedCustomerIds.size === 0) {
    return [];
  }

  const customers = await db
    .select({
      id: crmCustomers.id,
      code: crmCustomers.code,
      name: crmCustomers.name
    })
    .from(crmCustomers)
    .where(
      and(
        eq(crmCustomers.organizationId, organizationId),
        inArray(crmCustomers.id, [...attributedCustomerIds]),
        isNull(crmCustomers.deletedAt)
      )
    );

  const customerMap = new Map(customers.map((customer) => [customer.id, customer]));
  const summaryMap = new Map<string, RevenueAttributionSummary>();

  for (const quotation of quotations) {
    const attributedParties = attributionByQuotation.get(quotation.id) ?? [];

    for (const party of attributedParties) {
      const customer = customerMap.get(party.customerId);

      if (!customer) {
        continue;
      }

      const existing = summaryMap.get(customer.id);

      if (existing) {
        existing.revenue += quotation.totalAmount;

        if (!existing.quotationIds.includes(quotation.id)) {
          existing.quotationIds.push(quotation.id);
          existing.quotationCount += 1;
        }

        continue;
      }

      summaryMap.set(customer.id, {
        customerId: customer.id,
        customerCode: customer.code,
        customerName: customer.name,
        roleCode,
        revenue: quotation.totalAmount,
        quotationCount: 1,
        quotationIds: [quotation.id]
      });
    }
  }

  return [...summaryMap.values()].sort((a, b) => {
    if (b.revenue !== a.revenue) {
      return b.revenue - a.revenue;
    }

    return a.customerName.localeCompare(b.customerName);
  });
}

export async function getRevenueByEndCustomer(
  organizationId: string,
  filters: RevenueAttributionFilters = {}
) {
  return getRevenueByRole(organizationId, 'end_customer', filters);
}

export async function getRevenueByBillingCustomer(
  organizationId: string,
  filters: RevenueAttributionFilters = {}
) {
  return getRevenueByRole(organizationId, 'billing_customer', filters);
}

export async function getRevenueByContractor(
  organizationId: string,
  filters: RevenueAttributionFilters = {}
) {
  return getRevenueByRole(organizationId, 'contractor', filters);
}

export async function getRevenueByConsultant(
  organizationId: string,
  filters: RevenueAttributionFilters = {}
) {
  return getRevenueByRole(organizationId, 'consultant', filters);
}
