import { readBeacons, writeBeacons } from '../../beacon/api/beaconsService'
import type { Beacon } from '../../beacon/model/types'
import { mockMachineStateHistory } from '../mock/mockMachineStateHistory'
import { mockMachines } from '../mock/mockMachines'
import type {
  Machine,
  MachineStateHistoryItem,
  MachineSortDirection,
  MachineSortField,
} from '../model/types'

const MACHINES_STORAGE_KEY = 'fleet-ble-monitor.machines'

export type MachinesQueryParams = {
  page?: number
  pageSize?: number
  query?: string
  sortDirection?: MachineSortDirection
  sortField?: MachineSortField
}

export type MachinesPage = {
  totalPages: number
  totalElements: number
  page: Machine[]
}

export type MachinePayload = {
  name: string
  licensePlate: string
  type: string
  department: string
  description: string
}

type StoredMachine = Machine & Record<string, unknown>

function cloneMachine(machine: Machine | StoredMachine): Machine {
  const normalizedMachine = { ...machine } as StoredMachine
  const legacyInventoryKey = 'inventory' + 'Number'

  delete normalizedMachine[legacyInventoryKey]

  return normalizedMachine as Machine
}

function readMachines(): Machine[] {
  if (typeof window === 'undefined') {
    return mockMachines.map(cloneMachine)
  }

  const storedMachines = window.localStorage.getItem(MACHINES_STORAGE_KEY)

  if (!storedMachines) {
    const initialMachines = mockMachines.map(cloneMachine)
    window.localStorage.setItem(MACHINES_STORAGE_KEY, JSON.stringify(initialMachines))

    return initialMachines
  }

  try {
    return (JSON.parse(storedMachines) as StoredMachine[]).map(cloneMachine)
  } catch {
    const initialMachines = mockMachines.map(cloneMachine)
    window.localStorage.setItem(MACHINES_STORAGE_KEY, JSON.stringify(initialMachines))

    return initialMachines
  }
}

function writeMachines(machines: Machine[]) {
  if (typeof window === 'undefined') return

  window.localStorage.setItem(MACHINES_STORAGE_KEY, JSON.stringify(machines))
  window.dispatchEvent(new Event('machines:change'))
}

function assertBeaconCanBeBound(
  beacons: Beacon[],
  beaconId: number,
  machineId: number,
) {
  const beacon = beacons.find((item) => item.id === beaconId)

  if (!beacon) {
    throw new Error('Маяк не найден')
  }

  if (
    beacon.status === 'BOUND' &&
    beacon.boundMachineId !== null &&
    beacon.boundMachineId !== machineId
  ) {
    throw new Error('Маяк уже привязан к другой АТТ')
  }
}

function updateBeaconBinding(
  beacons: Beacon[],
  machineId: number,
  nextBeaconId: number | null,
) {
  return beacons.map((beacon) => {
    if (beacon.boundMachineId === machineId) {
      return {
        ...beacon,
        status: 'FREE',
        boundMachineId: null,
      } satisfies Beacon
    }

    if (nextBeaconId !== null && beacon.id === nextBeaconId) {
      return {
        ...beacon,
        status: 'BOUND',
        boundMachineId: machineId,
      } satisfies Beacon
    }

    return beacon
  })
}

function normalizeMachinePayload(payload: MachinePayload) {
  return {
    name: payload.name.trim(),
    licensePlate: payload.licensePlate.trim(),
    type: payload.type.trim(),
    department: payload.department.trim(),
    description: payload.description.trim(),
  }
}

function sortMachines(
  machines: Machine[],
  sortField: MachineSortField,
  sortDirection: MachineSortDirection,
) {
  const direction = sortDirection === 'ASC' ? 1 : -1

  return [...machines].sort((first, second) => {
    if (sortField === 'ID') {
      return (first.id - second.id) * direction
    }

    if (sortField === 'Date') {
      return (
        (new Date(first.createdAt).getTime() -
          new Date(second.createdAt).getTime()) *
        direction
      )
    }

    return first.name.localeCompare(second.name, 'ru') * direction
  })
}

function cloneHistoryItem(
  item: MachineStateHistoryItem,
): MachineStateHistoryItem {
  return { ...item }
}

export const machinesService = {
  async getMachines({
    page = 0,
    pageSize = 10,
    query,
    sortDirection = 'DESC',
    sortField = 'Date',
  }: MachinesQueryParams = {}): Promise<MachinesPage> {
    const normalizedQuery = query?.trim().toLowerCase() ?? ''

    const filteredMachines = readMachines().filter((machine) => {
      if (!normalizedQuery) return true

      return [
        machine.name,
        machine.licensePlate,
        machine.type,
        machine.department,
      ]
        .filter(Boolean)
        .join(' ')
        .toLowerCase()
        .includes(normalizedQuery)
    })

    const sortedMachines = sortMachines(filteredMachines, sortField, sortDirection)
    const totalPages = Math.ceil(sortedMachines.length / pageSize)
    const safePage = Math.min(Math.max(page, 0), Math.max(totalPages - 1, 0))
    const pageStart = safePage * pageSize

    return {
      totalPages,
      totalElements: sortedMachines.length,
      page: sortedMachines.slice(pageStart, pageStart + pageSize).map(cloneMachine),
    }
  },

  async getMachineById(id: number | string): Promise<Machine | null> {
    const numericId = Number(id)
    const machine = readMachines().find((item) => item.id === numericId)

    return machine ? cloneMachine(machine) : null
  },

  async getMachineBeacon(id: number | string): Promise<Beacon | null> {
    const numericId = Number(id)
    const machine = readMachines().find((item) => item.id === numericId)

    if (!machine?.beaconId) return null

    const beacon = readBeacons().find((item) => (
      item.id === machine.beaconId ||
      item.boundMachineId === numericId
    ))

    return beacon ? { ...beacon } : null
  },

  async getMachineStateHistory(
    machineId: number | string,
  ): Promise<MachineStateHistoryItem[]> {
    const numericMachineId = Number(machineId)

    return mockMachineStateHistory
      .filter((item) => item.machineId === numericMachineId)
      .sort((first, second) => (
        new Date(second.startedAt).getTime() -
        new Date(first.startedAt).getTime()
      ))
      .map(cloneHistoryItem)
  },

  async createMachine(payload: MachinePayload): Promise<Machine> {
    const machines = readMachines()
    const nextId = Math.max(0, ...machines.map((machine) => machine.id)) + 1
    const normalizedPayload = normalizeMachinePayload(payload)
    const now = new Date().toISOString()

    const machine: Machine = {
      id: nextId,
      ...normalizedPayload,
      beaconId: null,
      lastKnownState: 'NO_DATA',
      lastDataReceivedAt: null,
      createdAt: now,
      updatedAt: now,
    }

    writeMachines([...machines, machine])

    return cloneMachine(machine)
  },

  async updateMachine(
    id: number | string,
    payload: MachinePayload,
  ): Promise<Machine> {
    const numericId = Number(id)
    const machines = readMachines()
    const machineIndex = machines.findIndex((machine) => machine.id === numericId)

    if (machineIndex === -1) {
      throw new Error('АТТ не найдена')
    }

    const normalizedPayload = normalizeMachinePayload(payload)

    const currentMachine = machines[machineIndex]
    const nextMachine: Machine = {
      ...currentMachine,
      ...normalizedPayload,
      updatedAt: new Date().toISOString(),
    }
    const nextMachines = [...machines]
    nextMachines[machineIndex] = nextMachine

    writeMachines(nextMachines)

    return cloneMachine(nextMachine)
  },

  async deleteMachine(id: number | string): Promise<boolean> {
    const numericId = Number(id)
    const machines = readMachines()
    const nextMachines = machines.filter((machine) => machine.id !== numericId)

    if (nextMachines.length === machines.length) {
      throw new Error('АТТ не найдена')
    }

    writeMachines(nextMachines)
    writeBeacons(updateBeaconBinding(readBeacons(), numericId, null))

    return true
  },

  async bindBeacon(
    machineId: number | string,
    beaconId: number | string,
  ): Promise<Machine> {
    const numericMachineId = Number(machineId)
    const numericBeaconId = Number(beaconId)
    const machines = readMachines()
    const beacons = readBeacons()
    const machineIndex = machines.findIndex((machine) => machine.id === numericMachineId)

    if (machineIndex === -1) {
      throw new Error('АТТ не найдена')
    }

    assertBeaconCanBeBound(beacons, numericBeaconId, numericMachineId)

    const currentMachine = machines[machineIndex]
    const now = new Date().toISOString()
    const nextMachine: Machine = {
      ...currentMachine,
      beaconId: numericBeaconId,
      lastKnownState:
        currentMachine.lastKnownState === 'NO_DATA'
          ? 'IDLE'
          : currentMachine.lastKnownState,
      lastDataReceivedAt: currentMachine.lastDataReceivedAt ?? now,
      updatedAt: now,
    }
    const nextMachines = [...machines]
    nextMachines[machineIndex] = nextMachine

    writeMachines(nextMachines)
    writeBeacons(updateBeaconBinding(beacons, numericMachineId, numericBeaconId))

    return cloneMachine(nextMachine)
  },

  async unbindBeacon(machineId: number | string): Promise<Machine> {
    const numericMachineId = Number(machineId)
    const machines = readMachines()
    const machineIndex = machines.findIndex((machine) => machine.id === numericMachineId)

    if (machineIndex === -1) {
      throw new Error('АТТ не найдена')
    }

    const currentMachine = machines[machineIndex]
    const nextMachine: Machine = {
      ...currentMachine,
      beaconId: null,
      lastKnownState: 'NO_DATA',
      lastDataReceivedAt: null,
      updatedAt: new Date().toISOString(),
    }
    const nextMachines = [...machines]
    nextMachines[machineIndex] = nextMachine

    writeMachines(nextMachines)
    writeBeacons(updateBeaconBinding(readBeacons(), numericMachineId, null))

    return cloneMachine(nextMachine)
  },
}
