import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'

import {
  executeSavingsOpportunityAction,
  executeSavingsRealizationAction,
  fetchCfoValueDashboard,
  fetchSavingsOpportunities,
  fetchSavingsOpportunity
} from '../services/analyticsValue.js'
import {
  buildBreakdownGroups,
  buildDataQualityRows,
  buildFunnelRows,
  buildGuardrailRows,
  buildOpportunityRows,
  buildTrendSeries,
  buildValueKpis,
  classifyCfoDashboardState,
  createEmptyValueFilters,
  createRequestId,
  isDashboardStale,
  readValueFiltersFromQuery,
  resolveRangeWindow,
  writeValueFiltersToQuery
} from '../views/scripts/cfoValueDashboardModel.js'
import {
  hasValueOpportunityQuery,
  normalizeValueOpportunityId,
  opportunityMatchesValueContext,
  readValueOpportunityId,
  shouldClearValueOpportunityError,
  writeValueOpportunityToQuery
} from '../views/scripts/cfoValueSourceLinks.js'

const PAGE_SIZE = 12

export function useCfoValueDashboard(options = {}) {
  const route = useRoute()
  const router = useRouter()
  const initialFilters = readValueFiltersFromQuery(route.query)
  const filterDraft = reactive({ ...initialFilters })
  const appliedFilters = ref({ ...initialFilters })
  const page = ref(readPage(route.query.value_page))
  const dashboard = ref(null)
  const dashboardLoading = ref(false)
  const dashboardError = ref(null)
  const opportunities = ref(emptyOpportunityPage())
  const opportunitiesLoading = ref(false)
  const opportunitiesError = ref(null)
  const selectedOpportunity = ref(null)
  const selectedOpportunityId = ref('')
  const selectedOpportunityLoading = ref(false)
  const selectedOpportunityError = ref(null)
  const detailOpen = ref(false)
  const mutationLoading = ref(false)
  const mutationError = ref(null)
  const mutationMessage = ref('')
  const trendCurrency = ref('')
  const activeBreakdownDimension = ref('department')
  let dashboardRequestSeq = 0
  let opportunityRequestSeq = 0
  let detailRequestSeq = 0

  const dateWindow = computed(() => resolveRangeWindow(
    options.activeRange,
    options.customRange || {}
  ))
  const dashboardState = computed(() => classifyCfoDashboardState({
    dashboard: dashboard.value,
    error: dashboardError.value,
    loading: dashboardLoading.value
  }))
  const dashboardStale = computed(() => isDashboardStale(dashboard.value || {}))
  const kpis = computed(() => buildValueKpis(dashboard.value || {}))
  const funnelRows = computed(() => buildFunnelRows(dashboard.value || {}))
  const trend = computed(() => buildTrendSeries(dashboard.value || {}, trendCurrency.value))
  const breakdownGroups = computed(() => buildBreakdownGroups(dashboard.value || {}))
  const activeBreakdown = computed(() => (
    breakdownGroups.value.find((group) => group.dimension === activeBreakdownDimension.value)
    || breakdownGroups.value[0]
    || { dimension: '', label: '暂无维度', items: [] }
  ))
  const guardrailRows = computed(() => buildGuardrailRows(dashboard.value || {}))
  const dataQualityRows = computed(() => buildDataQualityRows(dashboard.value || {}))
  const opportunityRows = computed(() => buildOpportunityRows(opportunities.value))
  const hasPartialEvidence = computed(() => (
    dashboardState.value === 'partial'
    || dataQualityRows.value.some((item) => item.count > 0)
  ))

  async function loadDashboard() {
    const requestSeq = ++dashboardRequestSeq
    dashboardLoading.value = true
    dashboardError.value = null
    try {
      const payload = await fetchCfoValueDashboard({
        ...dateWindow.value,
        asOf: new Date().toISOString(),
        ...withoutStatus(appliedFilters.value)
      })
      if (requestSeq !== dashboardRequestSeq) return
      dashboard.value = payload
      if (!trend.value.currencies.includes(trendCurrency.value)) {
        trendCurrency.value = trend.value.currencies[0] || ''
      }
      if (!breakdownGroups.value.some((group) => group.dimension === activeBreakdownDimension.value)) {
        activeBreakdownDimension.value = breakdownGroups.value[0]?.dimension || ''
      }
    } catch (error) {
      if (requestSeq === dashboardRequestSeq) dashboardError.value = error
    } finally {
      if (requestSeq === dashboardRequestSeq) dashboardLoading.value = false
    }
  }

  async function loadOpportunities() {
    const requestSeq = ++opportunityRequestSeq
    opportunitiesLoading.value = true
    opportunitiesError.value = null
    try {
      const payload = await fetchSavingsOpportunities({
        ...appliedFilters.value,
        page: page.value,
        pageSize: PAGE_SIZE,
        createdFrom: dateWindow.value.start,
        createdTo: dateWindow.value.end,
        sort: 'created_desc'
      })
      if (requestSeq === opportunityRequestSeq) {
        opportunities.value = payload
        const lastPage = Math.max(1, Number(payload.totalPages || 0))
        if (page.value > lastPage) void setPage(lastPage)
      }
    } catch (error) {
      if (requestSeq === opportunityRequestSeq) opportunitiesError.value = error
    } finally {
      if (requestSeq === opportunityRequestSeq) opportunitiesLoading.value = false
    }
  }

  async function reloadAll() {
    await Promise.allSettled([loadDashboard(), loadOpportunities()])
  }

  async function loadOpportunityDetail(opportunityId) {
    const normalizedId = normalizeValueOpportunityId(opportunityId)
    if (!normalizedId) {
      await clearUnavailableOpportunity()
      return
    }
    const requestSeq = ++detailRequestSeq
    selectedOpportunityId.value = normalizedId
    detailOpen.value = true
    selectedOpportunity.value = null
    selectedOpportunityError.value = null
    selectedOpportunityLoading.value = true
    try {
      const payload = await fetchSavingsOpportunity(normalizedId)
      if (requestSeq !== detailRequestSeq) return
      if (!opportunityMatchesValueContext(payload, appliedFilters.value, dateWindow.value)) {
        await clearUnavailableOpportunity()
        return
      }
      selectedOpportunity.value = payload
    } catch (error) {
      if (requestSeq !== detailRequestSeq) return
      if (shouldClearValueOpportunityError(error)) {
        await clearUnavailableOpportunity()
        return
      }
      selectedOpportunityError.value = error
    } finally {
      if (requestSeq === detailRequestSeq) selectedOpportunityLoading.value = false
    }
  }

  async function openOpportunity(opportunityId) {
    const normalizedId = normalizeValueOpportunityId(opportunityId)
    if (!normalizedId) {
      await clearUnavailableOpportunity()
      return
    }
    const nextQuery = writeValueOpportunityToQuery(route.query, normalizedId)
    if (sameQuery(nextQuery, route.query)) {
      await loadOpportunityDetail(normalizedId)
      return
    }
    await router.push({ query: nextQuery })
  }

  async function closeOpportunity() {
    resetOpportunityDetail()
    const nextQuery = writeValueOpportunityToQuery(route.query)
    if (!sameQuery(nextQuery, route.query)) await router.push({ query: nextQuery })
  }

  function resetOpportunityDetail() {
    detailRequestSeq += 1
    detailOpen.value = false
    selectedOpportunity.value = null
    selectedOpportunityId.value = ''
    selectedOpportunityError.value = null
    selectedOpportunityLoading.value = false
  }

  async function clearUnavailableOpportunity() {
    resetOpportunityDetail()
    const nextQuery = writeValueOpportunityToQuery(route.query)
    if (!sameQuery(nextQuery, route.query)) await router.replace({ query: nextQuery })
  }

  async function syncOpportunityFromRoute() {
    const opportunityId = readValueOpportunityId(route.query)
    if (!opportunityId) {
      resetOpportunityDetail()
      if (hasValueOpportunityQuery(route.query)) await clearUnavailableOpportunity()
      return
    }
    if (
      opportunityId === selectedOpportunityId.value
      && selectedOpportunity.value
      && opportunityMatchesValueContext(selectedOpportunity.value, appliedFilters.value, dateWindow.value)
    ) {
      detailOpen.value = true
      return
    }
    if (opportunityId === selectedOpportunityId.value && selectedOpportunityLoading.value) return
    await loadOpportunityDetail(opportunityId)
  }

  async function submitAction(command = {}) {
    mutationLoading.value = true
    mutationError.value = null
    mutationMessage.value = ''
    const requestId = command.requestId || createRequestId('cfo-value')
    try {
      let response
      if (command.kind === 'record' || command.action === 'record_realization') {
        throw new Error('实际结果必须由付款事件或具备可追溯凭证的连接器写入。')
      } else if (command.kind === 'realization') {
        response = await executeSavingsRealizationAction(command.realizationId, {
          action: command.action,
          requestId,
          expectedVersion: command.expectedVersion,
          comment: command.comment,
          ...(command.action === 'reverse' ? { reversalAmount: command.reversalAmount } : {}),
          evidence: []
        })
      } else {
        response = await executeSavingsOpportunityAction(command.opportunityId, {
          action: command.action,
          requestId,
          expectedVersion: command.expectedVersion,
          comment: command.comment
        })
      }
      mutationMessage.value = response.replayed ? '请求已处理，本次返回原执行结果。' : '操作已完成并写入价值证据链。'
      selectedOpportunity.value = response.opportunity || selectedOpportunity.value
      await reloadAll()
      if (detailOpen.value && command.opportunityId) {
        await loadOpportunityDetail(command.opportunityId)
      }
      return response
    } catch (error) {
      mutationError.value = error
      throw error
    } finally {
      mutationLoading.value = false
    }
  }

  async function applyFilters() {
    const nextFilters = Object.fromEntries(
      Object.entries(filterDraft).map(([key, value]) => [key, String(value || '').trim()])
    )
    const nextQuery = writeValueFiltersToQuery(route.query, nextFilters)
    delete nextQuery.value_page
    if (sameQuery(nextQuery, route.query)) {
      appliedFilters.value = nextFilters
      page.value = 1
      await reloadAll()
      return
    }
    await router.replace({ query: nextQuery })
  }

  async function resetFilters() {
    Object.assign(filterDraft, createEmptyValueFilters())
    await applyFilters()
  }

  async function setPage(nextPage) {
    const normalized = Math.max(1, Number(nextPage || 1))
    const nextQuery = { ...route.query }
    if (normalized > 1) nextQuery.value_page = String(normalized)
    else delete nextQuery.value_page
    if (sameQuery(nextQuery, route.query)) {
      page.value = normalized
      await loadOpportunities()
      return
    }
    await router.replace({ query: nextQuery })
  }

  watch(
    () => [
      valueFilterRouteSignature(route.query),
      readPage(route.query.value_page),
      valueOpportunityRouteSignature(route.query)
    ],
    ([filterSignature, nextPage, opportunitySignature], previous = []) => {
      const [previousFilterSignature, previousPage] = previous
      const nextFilters = readValueFiltersFromQuery(route.query)
      Object.assign(filterDraft, nextFilters)
      appliedFilters.value = nextFilters
      page.value = nextPage
      if (filterSignature !== previousFilterSignature) {
        void reloadAll().then(() => syncOpportunityFromRoute())
      } else if (nextPage !== previousPage) {
        void loadOpportunities()
      }
      if (filterSignature === previousFilterSignature && opportunitySignature !== previous[2]) {
        void syncOpportunityFromRoute()
      }
    }
  )

  watch(
    () => [options.activeRange, options.customRange?.start, options.customRange?.end],
    () => {
      void Promise.allSettled([loadDashboard(), setPage(1)]).then(() => syncOpportunityFromRoute())
    }
  )

  watch(() => trend.value.selectedCurrency, (currency) => {
    if (currency && currency !== trendCurrency.value) trendCurrency.value = currency
  }, { immediate: true })

  onMounted(() => {
    void reloadAll().then(() => syncOpportunityFromRoute())
  })

  return {
    activeBreakdown,
    activeBreakdownDimension,
    appliedFilters,
    breakdownGroups,
    dashboard,
    dashboardError,
    dashboardLoading,
    dashboardState,
    dashboardStale,
    dataQualityRows,
    dateWindow,
    detailOpen,
    filterDraft,
    funnelRows,
    guardrailRows,
    hasPartialEvidence,
    kpis,
    mutationError,
    mutationLoading,
    mutationMessage,
    opportunities,
    opportunitiesError,
    opportunitiesLoading,
    opportunityRows,
    page,
    selectedOpportunity,
    selectedOpportunityId,
    selectedOpportunityError,
    selectedOpportunityLoading,
    trend,
    trendCurrency,
    applyFilters,
    closeOpportunity,
    loadDashboard,
    loadOpportunities,
    openOpportunity,
    reloadAll,
    resetFilters,
    setPage,
    submitAction
  }
}

function emptyOpportunityPage() {
  return { items: [], total: 0, page: 1, pageSize: PAGE_SIZE, totalPages: 0, generatedAt: '' }
}

function withoutStatus(filters = {}) {
  const { status: _status, ...dashboardFilters } = filters
  return dashboardFilters
}

function readPage(value) {
  const raw = Array.isArray(value) ? value[0] : value
  const page = Number(raw || 1)
  return Number.isInteger(page) && page > 0 ? page : 1
}

function valueFilterRouteSignature(query = {}) {
  return JSON.stringify(
    Object.fromEntries(
      Object.entries(query)
        .filter(([key]) => key.startsWith('value_') && !['value_page', 'value_opportunity'].includes(key))
        .sort(([left], [right]) => left.localeCompare(right))
    )
  )
}

function valueOpportunityRouteSignature(query = {}) {
  if (!Object.hasOwn(query, 'value_opportunity')) return ''
  const value = query.value_opportunity
  return JSON.stringify(Array.isArray(value) ? value.map(String) : String(value || ''))
}

function sameQuery(left = {}, right = {}) {
  const normalize = (query) => Object.entries(query)
    .map(([key, value]) => [key, Array.isArray(value) ? value.map(String) : String(value)])
    .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
  return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right))
}
