import { useState, useEffect, useCallback, useMemo } from 'react'
import Header                 from './components/Header'
import FailedBanner           from './components/FailedBanner'
import Calendar               from './components/Calendar'
import RightPanel             from './components/RightPanel'
import SettingsModal          from './components/SettingsModal'
import TestWindowDetailsPanel from './components/TestWindowDetailsPanel'
import { api, groupScheduleItems } from './api'

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function getMondayOfWeek(date) {
  const d = new Date(date)
  const day = d.getDay()
  d.setDate(d.getDate() + (day === 0 ? -6 : 1 - day))
  d.setHours(0, 0, 0, 0)
  return d
}

function parseLocalDate(value) {
  if (!value) return null

  const [year, month, day] = value.split('-').map(Number)
  if (!year || !month || !day) return null

  const parsed = new Date(year, month - 1, day)
  parsed.setHours(0, 0, 0, 0)
  return Number.isNaN(parsed.getTime()) ? null : parsed
}

function toKey(d) { return d.toISOString().slice(0, 10) }

function addDays(date, n) {
  const d = new Date(date)
  d.setDate(d.getDate() + n)
  return d
}

function parseHolidayDates(raw = '') {
  return new Set(
    String(raw)
      .split(',')
      .map((token) => token.trim())
      .filter(Boolean),
  )
}

function isOffDay(date, holidaySet) {
  const weekday = date.getDay()
  if (weekday === 0 || weekday === 6) return true
  return holidaySet.has(toKey(date))
}

function getWeekendStartDatesForWeek(weekStart, holidaySet) {
  const dates = []
  for (let offset = 0; offset < 7; offset += 1) {
    const current = addDays(weekStart, offset)
    const nextDay = addDays(current, 1)
    if (!isOffDay(current, holidaySet) && isOffDay(nextDay, holidaySet)) {
      dates.push(toKey(current))
    }
  }
  return dates
}

function getTonightWindowKeys() {
  const now = new Date()
  const hour = now.getHours()
  const today = new Date(now)
  today.setHours(0, 0, 0, 0)

  if (hour < 1) {
    return {
      shift3Date: toKey(addDays(today, -1)),
      shift1Date: toKey(today),
    }
  }

  return {
    shift3Date: toKey(today),
    shift1Date: toKey(addDays(today, 1)),
  }
}

function toTonightConfigRows(scheduleData) {
  const { shift3Date, shift1Date } = getTonightWindowKeys()
  const tonightTests = [
    ...((scheduleData[shift3Date]?.shift3) ?? []),
    ...((scheduleData[shift1Date]?.shift1) ?? []),
  ]

  const bySta = new Map()

  for (const test of tonightTests) {
    const config = test.config ?? {}
    for (const entry of Object.values(config)) {
      const staRaw = entry?.sta
      const testPoint = entry?.test_point
      if (!staRaw || !testPoint) continue

      for (const staPart of String(staRaw).split(',')) {
        const sta = staPart.trim().toUpperCase()
        if (!sta) continue

        if (!bySta.has(sta)) {
          bySta.set(sta, { sta, points: new Set() })
        }
        bySta.get(sta).points.add(testPoint)
      }
    }
  }

  return Array.from(bySta.values())
    .map((row) => ({
      sta: row.sta,
      testPoints: Array.from(row.points).sort(),
    }))
    .sort((a, b) => a.sta.localeCompare(b.sta))
}

const DEFAULT_SETTINGS = {
  p2pCoeCsvPath:      '',
  p3pCsvPath:         '',
  refResultDir:       '',
  dutResultDir:       '',
  smbUsername:        '',
  smbPassword:        '',
  smbDomain:          '',
  dutP2pRuntimeMinutes: '',
  dutCoeRuntimeMinutes: '',
  dutP3pRuntimeMinutes: '',
  refP2pRuntimeMinutes: '',
  refCoeRuntimeMinutes: '',
  refP3pRuntimeMinutes: '',
  testExclusion:      '',
  holidays:           '',
}

function sanitizeSettings(saved = {}) {
  const {
    p2pRuntimeMinutes,
    coeRuntimeMinutes,
    p3pRuntimeMinutes,
    ...rest
  } = saved

  delete rest.startDateOverride

  return {
    ...DEFAULT_SETTINGS,
    dutP2pRuntimeMinutes: rest.dutP2pRuntimeMinutes ?? p2pRuntimeMinutes ?? '',
    dutCoeRuntimeMinutes: rest.dutCoeRuntimeMinutes ?? coeRuntimeMinutes ?? '',
    dutP3pRuntimeMinutes: rest.dutP3pRuntimeMinutes ?? p3pRuntimeMinutes ?? '',
    refP2pRuntimeMinutes: rest.refP2pRuntimeMinutes ?? p2pRuntimeMinutes ?? '',
    refCoeRuntimeMinutes: rest.refCoeRuntimeMinutes ?? coeRuntimeMinutes ?? '',
    refP3pRuntimeMinutes: rest.refP3pRuntimeMinutes ?? p3pRuntimeMinutes ?? '',
    ...rest,
  }
}

// ---------------------------------------------------------------------------

export default function App() {
  const [settingsOpen,   setSettingsOpen]   = useState(false)
  const [settings,       setSettings]       = useState(DEFAULT_SETTINGS)
  const [dualDeviceWeekendWeekSelections, setDualDeviceWeekendWeekSelections] = useState({})
  const [topPriority,    setTopPriority]    = useState('')
  const [lowestPriority, setLowestPriority] = useState('')
  const [startDateOverride, setStartDateOverride] = useState('')
  const [failedTests,       setFailedTests]       = useState([])
  const [scheduleData,      setScheduleData]      = useState({})
  const [scheduleWindows,   setScheduleWindows]   = useState([])
  const [selectedWindowId,  setSelectedWindowId]  = useState(null)
  const [completionDate,    setCompletionDate]    = useState(null)
  const [weekStart,         setWeekStart]         = useState(() => getMondayOfWeek(new Date()))
  const [loading,           setLoading]           = useState(false)
  const [error,             setError]             = useState(null)
  const tonightConfigRows = useMemo(() => toTonightConfigRows(scheduleData), [scheduleData])
  const windowLookup = useMemo(() => {
    const lookup = new Map()

    for (const windowDetails of scheduleWindows) {
      for (const segment of windowDetails.segments ?? []) {
        lookup.set(`${segment.date}::shift${segment.shift_index}`, windowDetails)
      }
    }

    return lookup
  }, [scheduleWindows])
  const selectedWindow = useMemo(
    () => scheduleWindows.find((windowDetails) => windowDetails.window_id === selectedWindowId) ?? null,
    [scheduleWindows, selectedWindowId],
  )
  const holidaySet = useMemo(() => parseHolidayDates(settings.holidays), [settings.holidays])
  const activeWeekKey = useMemo(() => toKey(weekStart), [weekStart])
  const dualDeviceWeekendWeekEnabled = Boolean(dualDeviceWeekendWeekSelections[activeWeekKey])
  const dualDeviceWindowStartDates = useMemo(() => {
    if (!dualDeviceWeekendWeekEnabled) return []
    return getWeekendStartDatesForWeek(weekStart, holidaySet)
  }, [dualDeviceWeekendWeekEnabled, weekStart, holidaySet])

  // Fetch schedule for the given weekStart (Monday)
  const fetchSchedule = useCallback(async (start) => {
    try {
      const data = await api.getScheduleWeek(toKey(start))
      setScheduleData(groupScheduleItems(data.items))
      setScheduleWindows(data.windows ?? [])
      setCompletionDate(data.completion_date ?? null)
    } catch (e) {
      console.error('Failed to fetch schedule:', e)
    }
  }, [])

  const fetchRerunTests = useCallback(async () => {
    try {
      const data = await api.getRerunTests()
      setFailedTests(data.tests ?? [])
    } catch (e) {
      console.error('Failed to fetch rerun tests:', e)
    }
  }, [])

  // On mount: load settings + holidays + schedule for today's week
  useEffect(() => {
    async function init() {
      try {
        const [saved, holidayData] = await Promise.all([
          api.getSettings(),
          api.getHolidays(),
        ])
        setSettings(() => ({
          ...sanitizeSettings(saved),
          holidays: (holidayData.dates ?? []).join(', '),
        }))
      } catch (e) {
        console.warn('Backend not reachable on load:', e.message)
      }
      await fetchSchedule(getMondayOfWeek(new Date()))
    }
    init()
  }, [fetchSchedule])

  // Refetch whenever the displayed week changes
  useEffect(() => {
    fetchSchedule(weekStart)
  }, [weekStart, fetchSchedule])

  // Keep calendar statuses fresh when backend watcher marks tests from new result folders.
  useEffect(() => {
    const timer = setInterval(() => {
      fetchSchedule(weekStart)
      fetchRerunTests()
    }, 5000)

    return () => clearInterval(timer)
  }, [weekStart, fetchSchedule, fetchRerunTests])

  // Load rerun tests on mount
  useEffect(() => {
    fetchRerunTests()
  }, [fetchRerunTests])

  // -------------------------------------------------------------------------

  async function handleSaveSettings(newSettings) {
    setLoading(true)
    setError(null)
    try {
      const sanitizedSettings = sanitizeSettings(newSettings)

      await api.saveSettings(sanitizedSettings)

      const holidayDates = (sanitizedSettings.holidays ?? '')
        .split(',').map(s => s.trim()).filter(Boolean)
      await api.saveHolidays(holidayDates)

      if (sanitizedSettings.p2pCoeCsvPath?.trim()) {
        await api.loadCsv(sanitizedSettings.p2pCoeCsvPath.trim())
      }
      if (sanitizedSettings.p3pCsvPath?.trim()) {
        await api.loadCsv(sanitizedSettings.p3pCsvPath.trim())
      }

      setSettings(sanitizedSettings)
    } catch (e) {
      setError(e.message)
    } finally {
      setLoading(false)
    }
  }

  async function handleRemakeSchedule() {
    setLoading(true)
    setError(null)
    try {
      const effectiveStartDate = startDateOverride.trim()
      const result = await api.compileSchedule({
        start_date: effectiveStartDate || null,
        dual_device_weekend_start_dates: dualDeviceWindowStartDates,
        top_priority_tests: topPriority.split(',').map(s => s.trim()).filter(Boolean),
        lowest_priority_tests: lowestPriority.split(',').map(s => s.trim()).filter(Boolean),
        rule: settings.testExclusion ?? '',
      })
      setCompletionDate(result.completion_date ?? null)

      if (effectiveStartDate) {
        const overrideDate = parseLocalDate(effectiveStartDate)
        if (overrideDate) {
          const overrideWeekStart = getMondayOfWeek(overrideDate)
          setWeekStart(overrideWeekStart)
          await fetchSchedule(overrideWeekStart)
        } else {
          await fetchSchedule(weekStart)
        }
      } else {
        await fetchSchedule(weekStart)
      }

      setStartDateOverride('')
    } catch (e) {
      setError(e.message)
    } finally {
      setLoading(false)
    }
  }

  function handleRerunDecision(_rerunDuringDay) {
    setTopPriority(prev => {
      const existing = prev.split(',').map(s => s.trim()).filter(Boolean)
      const incoming = failedTests.map(t => t.test_id)
      const merged = [...new Set([...existing, ...incoming])]
      return merged.join(', ')
    })
    setFailedTests([])
  }

  useEffect(() => {
    if (selectedWindowId && !selectedWindow) {
      setSelectedWindowId(null)
    }
  }, [selectedWindow, selectedWindowId])

  function handleWindowSelect(windowDetails) {
    setSelectedWindowId(windowDetails?.window_id ?? null)
  }

  function handleCloseWindowDetails() {
    setSelectedWindowId(null)
  }

  // -------------------------------------------------------------------------

  return (
    <div className="flex flex-col min-h-screen bg-gray-950 text-gray-100">
      <Header onOpenSettings={() => setSettingsOpen(true)} />

      {error && (
        <div className="px-6 py-2 bg-orange-900/60 border-b border-orange-700 text-orange-200 text-sm flex items-center justify-between">
          <span>{error}</span>
          <button className="ml-3 text-orange-400 hover:text-white" onClick={() => setError(null)}>✕</button>
        </div>
      )}

      <FailedBanner
        rerunTests={failedTests}
        onDecision={handleRerunDecision}
      />

      <main className="relative flex flex-1 gap-4 p-4 overflow-hidden">
        <div className="flex-1 min-w-0 flex flex-col">
          <Calendar
            scheduleData={scheduleData}
            daytimeDateKey={null}
            weekStart={weekStart}
            onWeekChange={setWeekStart}
            dualDeviceWeekendWeekEnabled={dualDeviceWeekendWeekEnabled}
            onDualDeviceWeekendWeekEnabledChange={(enabled) => {
              setDualDeviceWeekendWeekSelections((prev) => ({
                ...prev,
                [activeWeekKey]: enabled,
              }))
            }}
            windowLookup={windowLookup}
            onWindowSelect={handleWindowSelect}
          />
        </div>

        <RightPanel
          completionDate={completionDate}
          startDateOverride={startDateOverride}
          onStartDateOverrideChange={setStartDateOverride}
          topPriority={topPriority}
          onTopPriorityChange={setTopPriority}
          lowestPriority={lowestPriority}
          onLowestPriorityChange={setLowestPriority}
          onRemakeSchedule={handleRemakeSchedule}
          loading={loading}
          tonightConfigRows={tonightConfigRows}
        />

        <TestWindowDetailsPanel
          isOpen={Boolean(selectedWindow)}
          windowDetails={selectedWindow}
          onClose={handleCloseWindowDetails}
        />
      </main>

      <SettingsModal
        isOpen={settingsOpen}
        onClose={() => setSettingsOpen(false)}
        settings={settings}
        onSave={handleSaveSettings}
      />
    </div>
  )
}
