import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { createBooking, fetchBookings } from '../api/bookings'
import { fetchPackageQuote } from '../api/pricing'
import { PACKAGES_3D2N_ONLY, PACKAGES_4D3N_ONLY, ROOM_TYPES } from '../constants'
import {
  buildDefaultRooms,
  calcBookingTotal,
  computeFees,
  defaultAgents,
  defaultEquipment,
  defaultInstructors,
  defaultTrips,
  genId,
  initialBkData,
  nights,
  today,
} from '../utils/helpers'

const defaultBk = () => ({ ...initialBkData(), addons: [], agent: null, fees: null })
const PACKAGES_4D3N_ONLY_SET = new Set(PACKAGES_4D3N_ONLY)
const PACKAGES_3D2N_ONLY_SET = new Set(PACKAGES_3D2N_ONLY)

export const usePosStore = create(
  persist(
    (set, get) => ({
      bookings: [],
      guests: [],
      rooms: buildDefaultRooms(),
      dives: [],
      equipment: defaultEquipment(),
      instructors: defaultInstructors(),
      agents: defaultAgents(),
      barSales: [],
      trips: defaultTrips(),
      events: [],
      transactions: [],

      activePage: 'dashboard',
      modalOpen: null,
      selectedRoom: null,
      currentBooking: null,
      receiptBooking: null,
      receiptTotals: null,

      barCart: [],
      barCat: 'Drinks',
      barRoomCharge: '',

      bkStep: 0,
      bkData: defaultBk(),
      agentValidateHtml: null,
      loadingBookings: false,
      bookingsLoaded: false,

      setPage: (activePage) => set({ activePage }),
      openModal: (modalOpen) => set({ modalOpen }),
      closeModal: () => set({ modalOpen: null }),

      patchBkData: (partial) =>
        set((s) => ({
          bkData: { ...s.bkData, ...partial },
          agentValidateHtml: null,
        })),

      resetBooking: () => set({ bkStep: 0, bkData: defaultBk(), agentValidateHtml: null }),

      setBkStep: (bkStep) => set({ bkStep }),

      hydrateBookingsFromApi: async () => {
        const { loadingBookings, bookingsLoaded } = get()
        if (loadingBookings || bookingsLoaded) return
        set({ loadingBookings: true })
        try {
          const rows = await fetchBookings()
          if (!Array.isArray(rows)) throw new Error('Invalid bookings response')
          set({
            bookings: rows,
            loadingBookings: false,
            bookingsLoaded: true,
          })
        } catch (e) {
          set({ loadingBookings: false })
          alert(e?.message || 'Failed to load bookings from database.')
        }
      },

      nextBkStep: () => set((s) => ({ bkStep: Math.min(s.bkStep + 1, 6) })),
      prevBkStep: () => set((s) => ({ bkStep: Math.max(s.bkStep - 1, 0) })),

      /** Step 4 -> 5: compute and store fees on enter */
      refreshBookingFees: () => {
        const { bkData, rooms } = get()
        const fees = computeFees(bkData, rooms)
        set({ bkData: { ...bkData, fees } })
      },

      loadDbPricingForSelection: async () => {
        const { bkData, rooms } = get()
        if (!bkData.room) throw new Error('Please select a room.')
        const room = rooms.find((r) => r.id === bkData.room)
        if (!room) throw new Error('Selected room not found.')
        const totalAdults = Math.max(1, bkData.pax?.adult || 1)
        const selectedRoomType = ROOM_TYPES.find((t) => t.type === room.type)
        const selectedRoomMax = selectedRoomType?.maxPax || room.maxPax || 0
        if (selectedRoomMax < totalAdults) {
          throw new Error(`Selected room supports up to ${selectedRoomMax} adults only. Please choose another room.`)
        }
        const assignments = Array.isArray(bkData.packageAssignments) ? bkData.packageAssignments : []
        const uniqueAssignments = [...new Set(assignments.filter(Boolean))]
        const hasMixedPackage = bkData.splitPackages && uniqueAssignments.length > 1
        const totalPax = Math.max(
          1,
          (bkData.pax?.adult || 1) + (bkData.pax?.kid || 0) + (bkData.pax?.elderly || 0)
        )

        if (hasMixedPackage) {
          const countsByPackage = assignments.reduce((acc, pkg) => {
            if (!pkg) return acc
            acc[pkg] = (acc[pkg] || 0) + 1
            return acc
          }, {})
          const quoteResults = await Promise.all(
            Object.entries(countsByPackage).map(async ([packageName, count]) => {
              const quote = await fetchPackageQuote({
                packageName,
                checkin: bkData.checkin,
                checkout: bkData.checkout,
                roomCode: room.code || room.id,
                // Package matrix: adults + elderly only; children via separate child stay fee.
                adult: bkData.pax?.adult ?? 1,
                kid: 0,
                elderly: bkData.pax?.elderly ?? 0,
                nationality: bkData.nationality,
              })
              const unitRate = Number(quote.baseRate) || 0
              return {
                packageName,
                count,
                unitRate,
                subtotal: unitRate * count,
                pricingId: quote.pricingId ?? null,
                roomType: quote.roomType ?? null,
              }
            })
          )
          const mixedPackageSubtotal = quoteResults.reduce((sum, q) => sum + q.subtotal, 0)
          const totalPax = Math.max(1, assignments.length)
          set((s) => ({
            bkData: {
              ...s.bkData,
              roomRate: mixedPackageSubtotal / totalPax,
              pricingMeta: {
                mixedPackages: true,
                assignments,
                quotesByPackage: quoteResults,
                mixedPackageSubtotal,
                roomCode: room.code || room.id,
                totalPax,
              },
            },
          }))
          return
        }

        const packageName = uniqueAssignments[0] || bkData.packageType
        if (!packageName) throw new Error('Please select a package.')

        const quote = await fetchPackageQuote({
          packageName,
          checkin: bkData.checkin,
          checkout: bkData.checkout,
          roomCode: room.code || room.id,
          adult: bkData.pax?.adult ?? 1,
          kid: 0,
          elderly: bkData.pax?.elderly ?? 0,
          nationality: bkData.nationality,
        })

        set((s) => ({
          bkData: {
            ...s.bkData,
            roomRate: Number(quote.baseRate) || 0,
            pricingMeta: quote,
          },
        }))
      },

      toggleAddon: (addon) => {
        const { bkData } = get()
        const addons = [...(bkData.addons || [])]
        const i = addons.findIndex((x) => x.id === addon.id)
        if (i >= 0) addons.splice(i, 1)
        else addons.push(addon)
        set({ bkData: { ...bkData, addons } })
      },

      validateAgent: () => {
        const { bkData, agents } = get()
        const code = (bkData.agentCode || '').trim()
        const ag = agents.find((a) => a.code === code)
        if (ag) {
          set({
            bkData: { ...bkData, agent: ag },
            agentValidateHtml: 'success',
          })
        } else if (code) {
          set({
            bkData: { ...bkData, agent: null },
            agentValidateHtml: 'warn',
          })
        } else {
          set({ bkData: { ...bkData, agent: null }, agentValidateHtml: null })
        }
      },

      advanceBkStep: async () => {
        const s = get()
        const { bkStep, bkData } = s
        if (bkStep === 0) {
          if (!bkData.checkin || !bkData.checkout) {
            alert('Please select check-in and check-out dates.')
            return
          }
          if (bkData.checkin >= bkData.checkout) {
            alert('Check-out must be after check-in.')
            return
          }
          const kid = bkData.pax?.kid ?? 0
          const kidInfant = bkData.pax?.kidInfant ?? 0
          if (kidInfant > kid) {
            alert('Children under 3 (free) cannot exceed total children.')
            return
          }
        }
        if (bkStep === 1 && !bkData.packageType) {
          alert('Please select a package.')
          return
        }
        if (bkStep === 1) {
          const selectedPackages = bkData.splitPackages
            ? (Array.isArray(bkData.packageAssignments) ? bkData.packageAssignments.filter(Boolean) : [])
            : [bkData.packageType]
          const stayNights = nights(bkData.checkin, bkData.checkout)
          const has4d3nOnlyPackage = selectedPackages.some((name) => PACKAGES_4D3N_ONLY_SET.has(name))
          const has3d2nOnlyPackage = selectedPackages.some((name) => PACKAGES_3D2N_ONLY_SET.has(name))
          if (has4d3nOnlyPackage && has3d2nOnlyPackage) {
            alert(
              '4D3N-only and 3D2N-only course packages cannot be combined on one stay. Use the same duration for everyone or change package selections.'
            )
            return
          }
          if (has4d3nOnlyPackage && stayNights !== 3) {
            alert(
              'Selected course package is only available for 4 days 3 nights (3 nights stay). Adjust dates or choose another package.'
            )
            return
          }
          if (has3d2nOnlyPackage && stayNights !== 2) {
            alert(
              'Selected course package is only available for 3 days 2 nights (2 nights stay). Adjust dates or choose another package.'
            )
            return
          }
        }
        if (bkStep === 2) {
          try {
            await s.loadDbPricingForSelection()
          } catch (e) {
            alert(e?.message || 'Failed to load package pricing from database.')
            return
          }
        }
        if (bkStep === 3) s.refreshBookingFees()
        if (bkStep === 5 && !bkData.guestName?.trim()) {
          alert('Please enter guest name.')
          return
        }
        s.nextBkStep()
      },

      confirmBooking: async () => {
        const { bkData, bookings, rooms, agents, transactions } = get()
        if (!bkData.guestName?.trim()) {
          alert('Guest name required.')
          return
        }
        if (!bkData.room) {
          alert('Please select a room (Step 3).')
          return
        }
        const t = calcBookingTotal(bkData)
        const bk = {
          id: genId('BK'),
          guestName: bkData.guestName,
          phone: bkData.phone,
          email: bkData.email,
          ic: bkData.ic,
          nationality: bkData.nationality,
          pricingId: bkData.pricingMeta?.pricingId ?? null,
          pax: bkData.pax,
          packageType: bkData.packageType,
          course: bkData.course,
          room: bkData.room,
          checkin: bkData.checkin,
          checkout: bkData.checkout,
          nights: t.n,
          splitPackages: !!bkData.splitPackages,
          packageAssignments: Array.isArray(bkData.packageAssignments) ? [...bkData.packageAssignments] : [],
          addons: bkData.addons,
          fees: bkData.fees,
          agentCode: bkData.agentCode,
          agentId: bkData.agent?.id,
          voucher: bkData.voucher,
          deposit: bkData.deposit ?? null,
          pricingMeta: bkData.pricingMeta ?? null,
          totalAmount: t.total,
          amountPaid: bkData.amountPaid ?? t.total,
          payMethod: bkData.payMethod,
          remarks: bkData.remarks,
          status: 'confirmed',
          createdAt: new Date().toISOString(),
        }
        const nextBookings = [...bookings, bk]
        const nextRooms = rooms.map((r) =>
          r.id === bkData.room ? { ...r, status: 'occupied', currentBooking: bk.id } : r
        )
        let nextAgents = agents
        if (bkData.agent) {
          nextAgents = agents.map((a) =>
            a.id === bkData.agent.id
              ? {
                  ...a,
                  bookings: a.bookings + 1,
                  earned:
                    a.earned +
                    (bkData.agent.type === 'percent'
                      ? t.total * (bkData.agent.rate / 100)
                      : bkData.agent.rate),
                }
              : a
          )
        }
        const nextTx = [
          ...transactions,
          {
            id: genId('TX'),
            bookingId: bk.id,
            guestName: bk.guestName,
            category: 'Booking',
            amount: bk.amountPaid,
            method: bk.payMethod,
            date: today(),
          },
        ]
        try {
          await createBooking({
            id: bk.id,
            guestName: bk.guestName,
            phone: bk.phone,
            email: bk.email,
            ic: bk.ic,
            nationality: bk.nationality,
            pricingId: bk.pricingId,
            pax: bk.pax,
            packageType: bk.packageType,
            course: bk.course,
            room: bk.room,
            checkin: bk.checkin,
            checkout: bk.checkout,
            nights: bk.nights,
            splitPackages: bk.splitPackages,
            packageAssignments: bk.packageAssignments,
            addons: bk.addons,
            fees: bk.fees,
            agentCode: bk.agentCode,
            agentId: bk.agentId,
            voucher: bk.voucher,
            deposit: bk.deposit,
            pricingMeta: bk.pricingMeta,
            totalAmount: bk.totalAmount,
            amountPaid: bk.amountPaid,
            payMethod: bk.payMethod,
            remarks: bk.remarks,
            status: bk.status,
          })
          set({
            bookings: nextBookings,
            rooms: nextRooms,
            agents: nextAgents,
            transactions: nextTx,
            bkStep: 0,
            bkData: defaultBk(),
            agentValidateHtml: null,
            receiptBooking: bk,
            receiptTotals: t,
            modalOpen: 'receipt',
          })
          alert(`✅ Booking Confirmed!\nID: ${bk.id}\nGuest: ${bk.guestName}\nTotal: RM ${t.total.toFixed(2)}`)
        } catch (e) {
          alert(e?.message || 'Failed to save booking to database.')
        }
      },

      viewBooking: (id) => {
        const key = String(id || '').trim()
        if (!key) return
        const b = get().bookings.find(
          (x) =>
            String(x.id || '').trim() === key ||
            String(x.bookingCode || '').trim() === key ||
            String(x.BookingCode || '').trim() === key
        )
        if (b) set({ currentBooking: b, modalOpen: 'booking-detail' })
      },

      doCheckin: () => {
        const b = get().currentBooking
        if (!b) return
        set((s) => ({
          bookings: s.bookings.map((x) => (x.id === b.id ? { ...x, status: 'checked-in' } : x)),
          modalOpen: null,
          currentBooking: null,
        }))
        alert(`✅ ${b.guestName} checked in to Room ${b.room}`)
      },

      showCheckoutModal: () => {
        set({ modalOpen: 'checkout' })
      },

      finalizeCheckout: (extra, disc, method) => {
        const b = get().currentBooking
        if (!b) return
        const finalAmt = Math.max(0, b.totalAmount + extra - disc - (b.amountPaid || 0))
        const newPaid = b.totalAmount + extra - disc
        set((s) => ({
          bookings: s.bookings.map((x) =>
            x.id === b.id
              ? {
                  ...x,
                  status: 'checked-out',
                  amountPaid: newPaid,
                }
              : x
          ),
          rooms: s.rooms.map((r) =>
            r.id === b.room ? { ...r, status: 'available', currentBooking: null } : r
          ),
          transactions: [
            ...s.transactions,
            {
              id: genId('TX'),
              bookingId: b.id,
              guestName: b.guestName,
              category: 'Checkout',
              amount: finalAmt,
              method,
              date: today(),
            },
          ],
          modalOpen: 'receipt',
          receiptBooking: { ...b, status: 'checked-out', amountPaid: newPaid },
          receiptTotals: { total: newPaid },
          currentBooking: null,
        }))
      },

      setBarCat: (barCat) => set({ barCat }),
      setBarRoomCharge: (barRoomCharge) => set({ barRoomCharge }),

      addBarCartItem: (item) => {
        const { barCart } = get()
        const ex = barCart.find((i) => i.name === item.name)
        if (ex) {
          set({
            barCart: barCart.map((i) => (i.name === item.name ? { ...i, qty: i.qty + 1 } : i)),
          })
        } else {
          set({ barCart: [...barCart, { ...item, qty: 1 }] })
        }
      },

      removeBarCartItem: (name) => {
        const { barCart } = get()
        const ex = barCart.find((i) => i.name === name)
        if (!ex) return
        if (ex.qty <= 1) {
          set({ barCart: barCart.filter((i) => i.name !== name) })
        } else {
          set({
            barCart: barCart.map((i) => (i.name === name ? { ...i, qty: i.qty - 1 } : i)),
          })
        }
      },

      clearBarCart: () => set({ barCart: [] }),

      processBarSale: () => {
        const { barCart, barRoomCharge, bookings, barSales, transactions } = get()
        if (!barCart.length) {
          alert('Cart is empty')
          return
        }
        const sub = barCart.reduce((s, i) => s + i.price * i.qty, 0)
        const total = sub * 1.06
        const guestName = barRoomCharge
          ? bookings.find((b) => b.id === barRoomCharge)?.guestName
          : 'Walk-in'
        let nextBookings = bookings
        if (barRoomCharge) {
          nextBookings = bookings.map((bk) =>
            bk.id === barRoomCharge
              ? { ...bk, totalAmount: bk.totalAmount + total, amountPaid: bk.amountPaid || 0 }
              : bk
          )
        }
        set({
          barSales: [
            ...barSales,
            {
              id: genId('BS'),
              items: barCart.map((i) => ({ ...i })),
              total,
              roomId: barRoomCharge || null,
              date: today(),
              time: new Date().toLocaleTimeString('en-MY'),
            },
          ],
          transactions: [
            ...transactions,
            {
              id: genId('TX'),
              category: 'Bar',
              amount: total,
              method: 'bar-pos',
              date: today(),
              guestName,
            },
          ],
          bookings: nextBookings,
          barCart: [],
          barRoomCharge: '',
        })
        alert(`✅ Sale processed: RM ${total.toFixed(2)}${barRoomCharge ? ' — charged to room' : ''}`)
      },

      showRoomDetail: (id) => {
        const r = get().rooms.find((x) => x.id === id)
        if (r) set({ selectedRoom: r, modalOpen: 'room' })
      },

      changeRoomStatus: (newStatus) => {
        const { selectedRoom, rooms } = get()
        if (!selectedRoom) return
        const cur = rooms.find((r) => r.id === selectedRoom.id)
        if (cur?.status === 'occupied' && newStatus !== 'occupied') {
          alert('Cannot change status of occupied room.')
          return
        }
        set({
          rooms: rooms.map((r) => (r.id === selectedRoom.id ? { ...r, status: newStatus } : r)),
          modalOpen: null,
          selectedRoom: null,
        })
      },

      bookFromRoomModal: () => {
        const { selectedRoom, rooms } = get()
        if (!selectedRoom) return
        const rm = rooms.find((r) => r.id === selectedRoom.id)
        const rate = rm?.rate?.weekday
        set({
          activePage: 'booking',
          modalOpen: null,
          selectedRoom: null,
          bkStep: 0,
          bkData: {
            ...defaultBk(),
            room: selectedRoom.id,
            roomRate: rate,
          },
        })
      },

      addDive: (dive) => {
        if (!dive.date) {
          alert('Select date')
          return
        }
        set((s) => ({
          dives: [...s.dives, { ...dive, id: genId('DV'), divers: 0, status: 'Scheduled' }],
          modalOpen: null,
        }))
      },

      cancelDive: (id) => {
        set((s) => ({ dives: s.dives.filter((d) => d.id !== id) }))
      },

      addEquipment: (e) => {
        if (!e.name?.trim()) {
          alert('Enter item name')
          return
        }
        set((s) => ({
          equipment: [
            ...s.equipment,
            {
              id: genId('EQ'),
              name: e.name,
              cat: e.cat,
              total: e.total,
              available: e.available,
              rate: e.rate,
            },
          ],
          modalOpen: null,
        }))
      },

      addInstructor: (i) => {
        if (!i.name?.trim()) {
          alert('Enter name')
          return
        }
        set((s) => ({
          instructors: [
            ...s.instructors,
            {
              id: genId('INS'),
              ...i,
              dives: 0,
              status: 'Active',
            },
          ],
          modalOpen: null,
        }))
      },

      addAgent: (a) => {
        if (!a.name?.trim() || !a.code?.trim()) {
          alert('Fill in name and code')
          return
        }
        set((s) => ({
          agents: [
            ...s.agents,
            {
              id: genId('AGT'),
              name: a.name,
              code: a.code,
              type: a.type,
              rate: a.rate,
              bookings: 0,
              earned: 0,
              status: 'Active',
            },
          ],
          modalOpen: null,
        }))
      },

      deactivateAgent: (id) => {
        set((s) => ({
          agents: s.agents.map((a) => (a.id === id ? { ...a, status: 'Inactive' } : a)),
        }))
      },

      addTrip: (t) => {
        if (!t.date || !t.route?.trim()) {
          alert('Fill in date and route')
          return
        }
        set((s) => ({
          trips: [...s.trips, { ...t, id: genId('TR'), booked: 0 }],
          modalOpen: null,
        }))
      },

      addEvent: (e) => {
        if (!e.name?.trim()) {
          alert('Enter event name')
          return
        }
        set((s) => ({
          events: [...s.events, { ...e, id: genId('EV'), status: 'Active' }],
          modalOpen: null,
        }))
      },

      payEventBalance: (id) => {
        const ev = get().events.find((x) => x.id === id)
        if (!ev) return
        const bal = ev.total - ev.deposit
        if (bal <= 0) {
          alert('Balance already cleared')
          return
        }
        set((s) => ({
          events: s.events.map((x) =>
            x.id === id ? { ...x, deposit: x.total, status: 'Paid' } : x
          ),
          transactions: [
            ...s.transactions,
            {
              id: genId('TX'),
              category: 'Event',
              amount: bal,
              method: 'cash',
              date: today(),
              guestName: ev.name,
            },
          ],
        }))
        alert(`✅ Balance of RM ${bal.toFixed(2)} paid for ${ev.name}`)
      },

      printCurrentReceipt: () => {
        const b = get().currentBooking
        if (b) {
          set({ receiptBooking: b, receiptTotals: { total: b.totalAmount }, modalOpen: 'receipt' })
        }
      },
    }),
    {
      name: 'one-tenggol-pos-storage',
      partialize: (s) => ({
        bookings: s.bookings,
        guests: s.guests,
        rooms: s.rooms,
        dives: s.dives,
        equipment: s.equipment,
        instructors: s.instructors,
        agents: s.agents,
        barSales: s.barSales,
        trips: s.trips,
        events: s.events,
        transactions: s.transactions,
      }),
    }
  )
)
