import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
import { ensurePricingMethodDetailRowsForServices } from '@/lib/pricingMethodTotals'
import { getServiceDictItemById } from '@/sql'
import {
  isSameNullableNumber,
  isSameScaleDetailRow,
  recomputeScaleDetailRow
} from '@/lib/pricingScaleDetail'
import {
  buildContractScaleIdMap,
  buildContractScaleMap,
  buildContractScaleProjectTotals,
  getContractScaleRowByMajor,
  getContractScaleProjectTotalsByRow,
  normalizeChangedScaleRowIds,
  resolveScaleRowMajorDictId as resolveRowMajorDictId
} from '@/lib/pricingScaleLink'
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
import { useKvStore } from '@/pinia/kv'
import { useZxFwPricingStore, type ServicePricingMethod, type ZxFwPricingField } from '@/pinia/zxFwPricing'

export type { ZxFwPricingField } from '@/pinia/zxFwPricing'

const PRICING_TOTALS_OPTIONS = { excludeInvestmentCostAndAreaRows: true } as const
const ONLY_COST_SCALE_ROW_ID = '__only-cost-scale-total__'

type ScaleDetailRow = {
  id: string
  projectIndex?: number
  majorDictId?: string
  amount?: number | null
  landArea?: number | null
  benchmarkBudget?: number | null
  benchmarkBudgetBasic?: number | null
  benchmarkBudgetOptional?: number | null
  benchmarkBudgetBasicChecked?: boolean
  benchmarkBudgetOptionalChecked?: boolean
  basicFormula?: string | null
  optionalFormula?: string | null
  consultCategoryFactor?: number | null
  majorFactor?: number | null
  workStageFactor?: number | null
  workRatio?: number | null
  budgetFee?: number | null
  budgetFeeBasic?: number | null
  budgetFeeOptional?: number | null
  path?: string[]
}

const getScaleMethodTotalBudgetFee = (rows: ScaleDetailRow[]) => {
  const hasValue = rows.some(row => typeof row.budgetFee === 'number' && Number.isFinite(row.budgetFee))
  if (!hasValue) return null
  return roundTo(sumByNumber(rows, row => row.budgetFee ?? null), 2)
}

type WorkloadDetailRow = {
  id: string
  conversion?: number | null
  workload?: number | null
  budgetAdoptedUnitPrice?: number | null
  consultCategoryFactor?: number | null
  basicFee?: number | null
  serviceFee?: number | null
}

type ServiceLite = {
  onlyCostScale?: boolean | null
}

const normalizeServiceIdSet = (serviceIds?: Array<string | number>) =>
  new Set((serviceIds || []).map(id => String(id || '').trim()).filter(Boolean))

const calcWorkloadBasicFee = (row: WorkloadDetailRow) => {
  if (String(row.id || '').startsWith('task-none-')) return null
  const price = row.budgetAdoptedUnitPrice
  const conversion = row.conversion
  const workload = row.workload
  if (
    typeof price !== 'number' ||
    !Number.isFinite(price) ||
    typeof conversion !== 'number' ||
    !Number.isFinite(conversion) ||
    typeof workload !== 'number' ||
    !Number.isFinite(workload)
  ) {
    return null
  }
  return roundTo(toDecimal(price).mul(conversion).mul(workload), 2)
}

const calcWorkloadServiceFee = (row: WorkloadDetailRow) => {
  if (String(row.id || '').startsWith('task-none-')) return null
  const factor = row.consultCategoryFactor
  const basicFee = calcWorkloadBasicFee(row)
  if (
    basicFee == null ||
    typeof factor !== 'number' ||
    !Number.isFinite(factor)
  ) {
    return null
  }
  return roundTo(toDecimal(basicFee).mul(factor), 2)
}

const getWorkloadMethodTotalServiceFee = (rows: WorkloadDetailRow[]) => {
  const hasValue = rows.some(row => typeof row.serviceFee === 'number' && Number.isFinite(row.serviceFee))
  if (!hasValue) return null
  return roundTo(sumByNumber(rows, row => row.serviceFee ?? null), 2)
}

const isOnlyCostScaleService = (serviceId: string) => {
  const service = getServiceDictItemById(serviceId) as ServiceLite | undefined
  return service?.onlyCostScale === true
}

const matchesChangedScaleRow = (
  row: ScaleDetailRow,
  changedRowIds?: Set<string>,
  options?: { bypassFilter?: boolean }
) => {
  if (options?.bypassFilter) return true
  if (!changedRowIds || changedRowIds.size === 0) return true
  const majorDictId = resolveRowMajorDictId(row)
  return Boolean(majorDictId && changedRowIds.has(majorDictId))
}

const syncScaleMethodRows = async (params: {
  contractId: string
  serviceId: string
  method: ServicePricingMethod
  sourceRowMap: Map<string, ScaleDetailRow>
  sourceRowIdMap: Map<string, ScaleDetailRow>
  projectTotals: Map<number, { amount: number | null; landArea: number | null }>
  changedRowIds?: Set<string>
}) => {
  const store = useZxFwPricingStore()
  const methodState = await store.loadServicePricingMethodState<ScaleDetailRow>(
    params.contractId,
    params.serviceId,
    params.method
  )
  if (!methodState?.detailRows?.length) return 0

  let changed = false
  let changedRowCount = 0
  const useSummaryScaleValues =
    methodState.detailRows.length === 1 ||
    (params.method === 'investScale' && isOnlyCostScaleService(params.serviceId))
  const nextRows = methodState.detailRows.map(rawRow => {
    const mode = params.method === 'investScale' ? 'cost' : 'area'
    if (!matchesChangedScaleRow(rawRow, params.changedRowIds, { bypassFilter: useSummaryScaleValues })) return rawRow
    const row = { ...rawRow }
    const sourceRow = getContractScaleRowByMajor(row, params.sourceRowMap, params.sourceRowIdMap)
    const projectTotals = getContractScaleProjectTotalsByRow(row, params.projectTotals)

    if (mode === 'cost') {
      const nextAmount = useSummaryScaleValues
        ? projectTotals.amount
        : (typeof sourceRow?.amount === 'number' ? sourceRow.amount : null)
      row.amount = isSameNullableNumber(row.amount, nextAmount) ? row.amount : nextAmount
    } else {
      const nextLandArea = useSummaryScaleValues
        ? projectTotals.landArea
        : (typeof sourceRow?.landArea === 'number' ? sourceRow.landArea : null)
      row.landArea = isSameNullableNumber(row.landArea, nextLandArea) ? row.landArea : nextLandArea
    }

    const recomputed = recomputeScaleDetailRow(row, mode)
    if (isSameScaleDetailRow(rawRow, recomputed, mode)) return rawRow
    changed = true
    changedRowCount += 1
    return recomputed
  })

  if (!changed) return 0

  store.setServicePricingMethodState(
    params.contractId,
    params.serviceId,
    params.method,
    {
      detailRows: nextRows,
      projectCount: methodState.projectCount ?? null
    },
    { force: true }
  )

  await syncPricingTotalToZxFw({
    contractId: params.contractId,
    serviceId: params.serviceId,
    field: params.method,
    value: getScaleMethodTotalBudgetFee(nextRows)
  })
  return changedRowCount
}

export interface ContractScaleSyncResult {
  updatedServiceCount: number
  updatedMethodCount: number
  updatedRowCount: number
}

export const syncContractScaleToPricing = async (
  contractId: string,
  options?: { changedRowIds?: string[] }
): Promise<ContractScaleSyncResult> => {
  const store = useZxFwPricingStore()
  const kvStore = useKvStore()
  await store.loadContract(contractId)
  const currentState = store.getContractState(contractId)
  const selectedIds = Array.from(new Set((currentState?.selectedIds || []).map(id => String(id || '').trim()).filter(Boolean)))
  const changedRowIdSet = options?.changedRowIds?.length
    ? normalizeChangedScaleRowIds(options.changedRowIds)
    : undefined
  if (selectedIds.length === 0) {
    return {
      updatedServiceCount: 0,
      updatedMethodCount: 0,
      updatedRowCount: 0
    }
  }
  if (changedRowIdSet && changedRowIdSet.size === 0) {
    return {
      updatedServiceCount: 0,
      updatedMethodCount: 0,
      updatedRowCount: 0
    }
  }

  await ensurePricingMethodDetailRowsForServices({
    contractId,
    serviceIds: selectedIds,
    options: PRICING_TOTALS_OPTIONS
  })

  const htData = await kvStore.getItem<{ detailRows?: ScaleDetailRow[]; totalAmount?: number | null }>(`ht-info-v3-${contractId}`)
  const sourceRows = Array.isArray(htData?.detailRows) ? htData.detailRows : []
  const sourceRowMap = buildContractScaleMap(sourceRows)
  const sourceRowIdMap = buildContractScaleIdMap(sourceRows)
  const projectTotals = buildContractScaleProjectTotals(sourceRows, htData?.totalAmount)
  const updatedServiceIdSet = new Set<string>()
  let updatedMethodCount = 0
  let updatedRowCount = 0

  for (const serviceId of selectedIds) {
    const investChangedCount = await syncScaleMethodRows({
      contractId,
      serviceId,
      method: 'investScale',
      sourceRowMap,
      sourceRowIdMap,
      projectTotals,
      changedRowIds: changedRowIdSet
    })
    if (investChangedCount > 0) {
      updatedServiceIdSet.add(serviceId)
      updatedMethodCount += 1
      updatedRowCount += investChangedCount
    }
    const landChangedCount = await syncScaleMethodRows({
      contractId,
      serviceId,
      method: 'landScale',
      sourceRowMap,
      sourceRowIdMap,
      projectTotals,
      changedRowIds: changedRowIdSet
    })
    if (landChangedCount > 0) {
      updatedServiceIdSet.add(serviceId)
      updatedMethodCount += 1
      updatedRowCount += landChangedCount
    }
  }

  return {
    updatedServiceCount: updatedServiceIdSet.size,
    updatedMethodCount,
    updatedRowCount
  }
}

const syncScaleMethodFactors = async (params: {
  contractId: string
  serviceId: string
  method: 'investScale' | 'landScale'
  syncConsultFactor: boolean
  consultFactor: number | null
  majorChangedRowIds?: Set<string>
  majorFactorMap: Map<string, number | null>
}) => {
  const store = useZxFwPricingStore()
  const methodState = await store.loadServicePricingMethodState<ScaleDetailRow>(
    params.contractId,
    params.serviceId,
    params.method
  )
  if (!methodState?.detailRows?.length) return 0

  let changed = false
  let changedRowCount = 0
  const mode = params.method === 'investScale' ? 'cost' : 'area'
  const nextRows = methodState.detailRows.map(rawRow => {
    const row = { ...rawRow }
    let rowChanged = false

    if (params.syncConsultFactor && !isSameNullableNumber(row.consultCategoryFactor, params.consultFactor)) {
      row.consultCategoryFactor = params.consultFactor
      rowChanged = true
    }

    if (params.majorChangedRowIds?.size) {
      const majorDictId = resolveRowMajorDictId(row)
      if (params.majorChangedRowIds.has(majorDictId)) {
        const nextMajorFactor = params.majorFactorMap.get(majorDictId) ?? null
        if (!isSameNullableNumber(row.majorFactor, nextMajorFactor)) {
          row.majorFactor = nextMajorFactor
          rowChanged = true
        }
      }
    }

    if (!rowChanged) return rawRow
    const recomputed = recomputeScaleDetailRow(row, mode)
    if (isSameScaleDetailRow(rawRow, recomputed, mode)) return rawRow
    changed = true
    changedRowCount += 1
    return recomputed
  })

  if (!changed) return 0

  store.setServicePricingMethodState(
    params.contractId,
    params.serviceId,
    params.method,
    {
      detailRows: nextRows,
      projectCount: methodState.projectCount ?? null
    },
    { force: true }
  )

  await syncPricingTotalToZxFw({
    contractId: params.contractId,
    serviceId: params.serviceId,
    field: params.method,
    value: getScaleMethodTotalBudgetFee(nextRows)
  })
  return changedRowCount
}

const syncWorkloadMethodConsultFactor = async (params: {
  contractId: string
  serviceId: string
  consultFactor: number | null
}) => {
  const store = useZxFwPricingStore()
  const methodState = await store.loadServicePricingMethodState<WorkloadDetailRow>(
    params.contractId,
    params.serviceId,
    'workload'
  )
  if (!methodState?.detailRows?.length) return 0

  let changed = false
  let changedRowCount = 0
  const nextRows = methodState.detailRows.map(rawRow => {
    const row = { ...rawRow }
    let rowChanged = false

    if (!isSameNullableNumber(row.consultCategoryFactor, params.consultFactor)) {
      row.consultCategoryFactor = params.consultFactor
      rowChanged = true
    }

    const nextBasicFee = calcWorkloadBasicFee(row)
    const nextServiceFee = calcWorkloadServiceFee(row)
    if (!isSameNullableNumber(row.basicFee, nextBasicFee)) {
      row.basicFee = nextBasicFee
      rowChanged = true
    }
    if (!isSameNullableNumber(row.serviceFee, nextServiceFee)) {
      row.serviceFee = nextServiceFee
      rowChanged = true
    }

    if (!rowChanged) return rawRow
    changed = true
    changedRowCount += 1
    return row
  })

  if (!changed) return 0

  store.setServicePricingMethodState(
    params.contractId,
    params.serviceId,
    'workload',
    {
      detailRows: nextRows,
      projectCount: methodState.projectCount ?? null
    },
    { force: true }
  )

  await syncPricingTotalToZxFw({
    contractId: params.contractId,
    serviceId: params.serviceId,
    field: 'workload',
    value: getWorkloadMethodTotalServiceFee(nextRows)
  })
  return changedRowCount
}

export interface ContractFactorSyncResult {
  updatedServiceCount: number
  updatedMethodCount: number
  updatedRowCount: number
}

export const syncContractFactorsToPricing = async (
  contractId: string,
  options?: {
    consultChangedServiceIds?: string[]
    majorChangedRowIds?: string[]
  }
): Promise<ContractFactorSyncResult> => {
  const store = useZxFwPricingStore()
  await store.loadContract(contractId)
  const currentState = store.getContractState(contractId)
  const selectedIds = Array.from(new Set((currentState?.selectedIds || []).map(id => String(id || '').trim()).filter(Boolean)))
  if (selectedIds.length === 0) {
    return { updatedServiceCount: 0, updatedMethodCount: 0, updatedRowCount: 0 }
  }

  const consultChangedServiceIdSet = normalizeServiceIdSet(options?.consultChangedServiceIds)
  const majorChangedRowIdSet = normalizeChangedScaleRowIds(options?.majorChangedRowIds)
  if (consultChangedServiceIdSet.size === 0 && majorChangedRowIdSet.size === 0) {
    return { updatedServiceCount: 0, updatedMethodCount: 0, updatedRowCount: 0 }
  }

  const [consultFactorMap, majorFactorMap] = await Promise.all([
    loadConsultCategoryFactorMap(`ht-consult-category-factor-v1-${contractId}`),
    loadMajorFactorMap(`ht-major-factor-v1-${contractId}`)
  ])

  let updatedMethodCount = 0
  let updatedRowCount = 0
  const updatedServiceIdSet = new Set<string>()

  for (const serviceId of selectedIds) {
    const syncConsultFactor = consultChangedServiceIdSet.has(serviceId)
    const syncMajorFactor = majorChangedRowIdSet.size > 0
    if (!syncConsultFactor && !syncMajorFactor) continue

    const consultFactor = consultFactorMap.get(serviceId) ?? null

    const investChangedCount = await syncScaleMethodFactors({
      contractId,
      serviceId,
      method: 'investScale',
      syncConsultFactor,
      consultFactor,
      majorChangedRowIds: syncMajorFactor ? majorChangedRowIdSet : undefined,
      majorFactorMap
    })
    if (investChangedCount > 0) {
      updatedServiceIdSet.add(serviceId)
      updatedMethodCount += 1
      updatedRowCount += investChangedCount
    }

    const landChangedCount = await syncScaleMethodFactors({
      contractId,
      serviceId,
      method: 'landScale',
      syncConsultFactor,
      consultFactor,
      majorChangedRowIds: syncMajorFactor ? majorChangedRowIdSet : undefined,
      majorFactorMap
    })
    if (landChangedCount > 0) {
      updatedServiceIdSet.add(serviceId)
      updatedMethodCount += 1
      updatedRowCount += landChangedCount
    }

    if (syncConsultFactor) {
      const workloadChangedCount = await syncWorkloadMethodConsultFactor({
        contractId,
        serviceId,
        consultFactor
      })
      if (workloadChangedCount > 0) {
        updatedServiceIdSet.add(serviceId)
        updatedMethodCount += 1
        updatedRowCount += workloadChangedCount
      }
    }
  }

  return {
    updatedServiceCount: updatedServiceIdSet.size,
    updatedMethodCount,
    updatedRowCount
  }
}

export const syncPricingTotalToZxFw = async (params: {
  contractId: string
  serviceId: string | number
  field: ZxFwPricingField
  value: number | null | undefined
}) => {
  const store = useZxFwPricingStore()
  return store.updatePricingField(params)
}
