import { promises as fs } from 'fs'
import path from 'path'
import { getProjectPath, getServerDataPath } from './paths.js'
import { error as loggerError, info as loggerInfo } from './logger.js'

const SPIELPLAN_HEADERS = [
  'Termin',
  'Timestamp',
  'Wochentag',
  'Verband',
  'Saison',
  'Meisterschaft',
  'Altersklasse',
  'Liga',
  'Staffel',
  'Runde',
  'BegegnungNr',
  'HalleNr',
  'HalleName',
  'HalleStrasse',
  'HallePLZ',
  'HalleOrt',
  'HeimVereinVerband',
  'HeimVereinNr',
  'HeimVereinName',
  'HeimMannschaftAltersklasse',
  'HeimMannschaftNr',
  'HeimMannschaft',
  'GastVereinVerband',
  'GastVereinNr',
  'GastVereinName',
  'GastMannschaftAltersklasse',
  'GastMannschaftNr',
  'GastMannschaft',
  'SpieleHeim',
  'SpieleGast'
]

const CLUB_ID = '43030'
const CLUB_NAME = 'Harheimer TC'
const SEASON_SLUG_PATTERN = /^\d{2}--\d{2}$/
const SEASON_FILE_PATTERN = /^spielplan-(\d{2}--\d{2})\.json$/

function formatGermanDateTimeFromTimestamp(timestamp) {
  if (!Number.isFinite(timestamp)) return ''

  const parts = new Intl.DateTimeFormat('de-DE', {
    timeZone: 'Europe/Berlin',
    day: '2-digit',
    month: '2-digit',
    year: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
    hour12: false
  }).formatToParts(new Date(timestamp * 1000))

  const values = Object.fromEntries(parts.map((part) => [part.type, part.value]))
  return `${values.day}.${values.month}.${values.year} ${values.hour}:${values.minute}`
}

function getMatchTimestamp(match) {
  if (Number.isFinite(match?.timestamp)) return match.timestamp

  const parsedDate = match?.date ? Date.parse(match.date) : NaN
  return Number.isNaN(parsedDate) ? null : Math.floor(parsedDate / 1000)
}

function seasonLabel(season) {
  if (!season?.startYear || !season?.endYear) return ''
  return `${season.startYear}/${String(season.endYear).slice(-2)}`
}

function seasonSlugToLabel(slug) {
  const match = String(slug || '').match(/^(\d{2})--(\d{2})$/)
  if (!match) return ''
  return `20${match[1]}/${match[2]}`
}

function logReadError(message, filePath, error) {
  loggerError(message, { filePath, error })
}

export function requireSeasonSlug(seasonSlug) {
  const value = String(seasonSlug || '')
  if (!SEASON_SLUG_PATTERN.test(value)) {
    throw new Error(`Ungueltiger Spielplan-Saison-Slug: ${value}`)
  }
  return value
}

export function validateSeasonSlug(seasonSlug) {
  try {
    requireSeasonSlug(seasonSlug)
    return true
  } catch (_e) {
    return false
  }
}

function normalizeSpielplanCsvPath(csvPath) {
  const value = String(csvPath || '')
  if (value.includes('\0') || path.basename(value) !== 'spielplan.csv') {
    throw new Error('Ungueltiger Spielplan-CSV-Pfad')
  }
  return path.normalize(value)
}

export function getCurrentSeasonSlug(date = new Date()) {
  const year = date.getFullYear()
  const month = date.getMonth()
  const startYear = month >= 6 ? year : year - 1
  const endYear = startYear + 1
  return `${String(startYear).slice(-2)}--${String(endYear).slice(-2)}`
}

export function inferSeasonSlugFromRows(data) {
  if (!Array.isArray(data)) return null

  for (const row of data) {
    const seasonValue = String(row?.Saison || '')
    const seasonMatch = seasonValue.match(/(\d{4})\/(\d{2})/)
    if (seasonMatch) {
      return `${seasonMatch[1].slice(-2)}--${seasonMatch[2]}`
    }
  }

  for (const row of data) {
    const timestamp = Number(row?.Timestamp)
    if (Number.isFinite(timestamp) && timestamp > 0) {
      return getCurrentSeasonSlug(new Date(timestamp * 1000))
    }

    const termin = String(row?.Termin || '').trim()
    const dateMatch = termin.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})/)
    if (dateMatch) {
      const [, day, month, year] = dateMatch
      return getCurrentSeasonSlug(new Date(Number(year), Number(month) - 1, Number(day)))
    }
  }

  return null
}

function inferAgeClass(match, teamName) {
  const haystack = [
    teamName,
    match.leagueName,
    match.leagueShortName
  ].filter(Boolean).join(' ').toLowerCase()

  if (haystack.includes('jugend') || /\bj\d{2}\b/.test(haystack)) {
    const ageMatch = haystack.match(/j(?:ugend\s*)?(\d{2})/)
    return ageMatch ? `Jugend ${ageMatch[1]}` : 'Jugend'
  }

  return 'Erwachsene'
}

function inferTeamNumber(teamName) {
  const normalized = String(teamName || '').trim()
  if (!normalized) return ''
  if (normalized === CLUB_NAME || normalized.includes('(J')) return '1'

  const match = normalized.match(/\b([IVX]+)\b$/i)
  if (!match) return '1'

  const values = { I: 1, V: 5, X: 10 }
  const roman = match[1].toUpperCase()
  let total = 0
  for (let i = 0; i < roman.length; i += 1) {
    const current = values[roman[i]] || 0
    const next = values[roman[i + 1]] || 0
    total += current < next ? -current : current
  }
  return String(total || '')
}

function splitResult(result) {
  const match = String(result || '').match(/^(\d+)\s*:\s*(\d+)$/)
  return {
    home: match ? match[1] : '',
    away: match ? match[2] : ''
  }
}

function isHarheimerMatch(match) {
  return match?.teamHomeClubId === CLUB_ID || match?.teamAwayClubId === CLUB_ID
}

export function validateImportedSpielplan(imported) {
  if (!imported || typeof imported !== 'object') {
    throw new Error('Import-Datei ist kein JSON-Objekt')
  }
  if (imported.source?.clubId !== CLUB_ID) {
    throw new Error(`Unerwartete Vereinsnummer: ${imported.source?.clubId || 'leer'}`)
  }
  if (!Array.isArray(imported.matches) || imported.matches.length === 0) {
    throw new Error('Import-Datei enthaelt keine Spiele')
  }
  if (imported.matchCount !== imported.matches.length) {
    throw new Error(`matchCount passt nicht zu matches.length (${imported.matchCount} != ${imported.matches.length})`)
  }

  imported.matches.forEach((match, index) => {
    for (const field of ['date', 'meetingId', 'teamHome', 'teamAway']) {
      if (!match[field]) {
        throw new Error(`Spiel ${index + 1}: Pflichtfeld ${field} fehlt`)
      }
    }
    if (!Number.isFinite(getMatchTimestamp(match))) {
      throw new Error(`Spiel ${index + 1}: timestamp/date fehlt oder ist ungueltig`)
    }
    if (!isHarheimerMatch(match)) {
      throw new Error(`Spiel ${index + 1}: keine Harheimer Beteiligung`)
    }
  })

  return true
}

export function convertImportedSpielplanToJson(imported) {
  validateImportedSpielplan(imported)

  const season = seasonLabel(imported.source?.season)
  const data = imported.matches.map((match) => {
    const result = splitResult(match.result)
    const timestamp = getMatchTimestamp(match)
    const homeIsHarheim = match.teamHomeClubId === CLUB_ID
    const awayIsHarheim = match.teamAwayClubId === CLUB_ID

    return {
      Termin: formatGermanDateTimeFromTimestamp(timestamp),
      Timestamp: String(timestamp),
      Wochentag: match.formattedDay ? match.formattedDay.split(',')[0] : '',
      Verband: match.leagueOrgShortName || imported.source?.association || '',
      Saison: season,
      Meisterschaft: season ? `Kreis Frankfurt ${season}` : '',
      Altersklasse: inferAgeClass(match, homeIsHarheim ? match.teamHome : match.teamAway),
      Liga: match.leagueName || '',
      Staffel: match.leagueShortName || '',
      Runde: match.roundName || '',
      BegegnungNr: match.meetingNumber || match.meetingId || '',
      HalleNr: match.hallNumber || '',
      HalleName: match.location?.label || '',
      HalleStrasse: match.location?.street || '',
      HallePLZ: match.location?.zip || '',
      HalleOrt: match.location?.city || '',
      HeimVereinVerband: match.leagueOrgShortName || imported.source?.association || '',
      HeimVereinNr: match.teamHomeClubId || '',
      HeimVereinName: homeIsHarheim ? CLUB_NAME : '',
      HeimMannschaftAltersklasse: inferAgeClass(match, match.teamHome),
      HeimMannschaftNr: inferTeamNumber(match.teamHome),
      HeimMannschaft: match.teamHome || '',
      GastVereinVerband: match.leagueOrgShortName || imported.source?.association || '',
      GastVereinNr: match.teamAwayClubId || '',
      GastVereinName: awayIsHarheim ? CLUB_NAME : '',
      GastMannschaftAltersklasse: inferAgeClass(match, match.teamAway),
      GastMannschaftNr: inferTeamNumber(match.teamAway),
      GastMannschaft: match.teamAway || '',
      SpieleHeim: result.home,
      SpieleGast: result.away
    }
  })

  return {
    format: 'harheimertc.spielplan.v1',
    sourceFormat: 'mytischtennis.import.v1',
    updatedAt: new Date().toISOString(),
    importedAt: imported.importedAt || null,
    source: imported.source,
    headers: SPIELPLAN_HEADERS,
    data
  }
}

export function parseDelimitedLine(line, delimiter) {
  const values = []
  let current = ''
  let inQuotes = false

  for (let i = 0; i < line.length; i += 1) {
    const char = line[i]
    const nextChar = line[i + 1]

    if (char === '"' && inQuotes && nextChar === '"') {
      current += '"'
      i += 1
    } else if (char === '"') {
      inQuotes = !inQuotes
    } else if (char === delimiter && !inQuotes) {
      values.push(current.trim())
      current = ''
    } else {
      current += char
    }
  }

  values.push(current.trim())
  return values
}

export function detectDelimiter(headerLine) {
  const tabCount = (headerLine.match(/\t/g) || []).length
  const semicolonCount = (headerLine.match(/;/g) || []).length
  const commaCount = (headerLine.match(/,/g) || []).length

  if (tabCount > semicolonCount && tabCount > commaCount) return '\t'
  if (commaCount > semicolonCount) return ','
  return ';'
}

export function parseSpielplanCsv(content) {
  const lines = content.split(/\r?\n/).filter(line => line.trim() !== '')
  if (lines.length < 2) {
    return { headers: [], data: [] }
  }

  const delimiter = detectDelimiter(lines[0])
  const headers = parseDelimitedLine(lines[0], delimiter)
  const data = lines.slice(1).map((line) => {
    const values = parseDelimitedLine(line, delimiter)
    const row = {}

    headers.forEach((header, index) => {
      row[header] = values[index] || ''
    })

    return row
  })

  return { headers, data }
}

function escapeCsvValue(value) {
  const stringValue = String(value ?? '')
  if (/[;"\n\r]/.test(stringValue)) {
    return `"${stringValue.replace(/"/g, '""')}"`
  }
  return stringValue
}

export function stringifySpielplanCsv(headers, data) {
  return [
    headers.map(escapeCsvValue).join(';'),
    ...data.map((row) => headers.map((header) => escapeCsvValue(row[header])).join(';'))
  ].join('\n')
}

export function createSpielplanJsonFromCsv(content) {
  const parsed = parseSpielplanCsv(content)
  const seasonSlug = inferSeasonSlugFromRows(parsed.data)
  return {
    format: 'harheimertc.spielplan.v1',
    updatedAt: new Date().toISOString(),
    source: seasonSlug
      ? {
          season: {
            seasonSlug,
            label: seasonSlugToLabel(seasonSlug)
          }
        }
      : undefined,
    headers: parsed.headers,
    data: parsed.data
  }
}

async function readFirstExistingJson(paths) {
  for (const filePath of paths) {
    try {
      const content = await fs.readFile(filePath, 'utf8')
      const parsed = JSON.parse(content)
      const headers = Array.isArray(parsed.headers) ? parsed.headers : SPIELPLAN_HEADERS
      const data = Array.isArray(parsed.data) ? parsed.data : []
      const fileName = path.basename(filePath)
      const season = parsed.source?.season?.seasonSlug || fileName.match(SEASON_FILE_PATTERN)?.[1] || null
      return {
        source: 'json',
        filePath,
        season,
        headers,
        data
      }
    } catch (error) {
      if (error.code !== 'ENOENT') {
        logReadError('Fehler beim Lesen der Spielplan-JSON', filePath, error)
      }
    }
  }

  return null
}

async function readFirstExistingCsv(paths) {
  for (const filePath of paths) {
    try {
      const content = await fs.readFile(filePath, 'utf8')
      const parsed = parseSpielplanCsv(content)
      return {
        source: 'csv',
        filePath,
        season: inferSeasonSlugFromRows(parsed.data),
        ...parsed
      }
    } catch (error) {
      if (error.code !== 'ENOENT') {
        logReadError('Fehler beim Lesen der Spielplan-CSV', filePath, error)
      }
    }
  }

  return null
}

async function readSeasonFileInfo(filePath, slug) {
  try {
    const content = await fs.readFile(filePath, 'utf8')
    const parsed = JSON.parse(content)
    return {
      slug,
      label: parsed.source?.season?.label || seasonSlugToLabel(slug),
      filePath,
      updatedAt: parsed.updatedAt || null,
      importedAt: parsed.importedAt || null,
      count: Array.isArray(parsed.data) ? parsed.data.length : 0
    }
  } catch (_error) {
    return {
      slug,
      label: seasonSlugToLabel(slug),
      filePath,
      updatedAt: null,
      importedAt: null,
      count: 0
    }
  }
}

export async function listSpielplanSeasons() {
  const directories = [
    getServerDataPath('public-data', 'spielplaene'),
    getProjectPath('public', 'data', 'spielplaene')
  ]
  const bySlug = new Map()

  for (const directory of directories) {
    let entries = []
    try {
      entries = await fs.readdir(directory)
    } catch (error) {
      if (error.code !== 'ENOENT') {
        logReadError('Fehler beim Lesen des Spielplan-Saisonverzeichnisses', directory, error)
      }
      continue
    }

    for (const entry of entries) {
      const match = entry.match(SEASON_FILE_PATTERN)
      if (!match || bySlug.has(match[1])) continue
      bySlug.set(match[1], await readSeasonFileInfo(path.join(directory, entry), match[1]))
    }
  }

  return [...bySlug.values()].sort((a, b) => b.slug.localeCompare(a.slug))
}

export async function getDefaultSpielplanSeason() {
  const seasons = await listSpielplanSeasons()
  const currentSeason = getCurrentSeasonSlug()
  return seasons.find((season) => season.slug === currentSeason)?.slug || seasons[0]?.slug || null
}

export async function readSpielplanData(options = {}) {
  const season = options.season && SEASON_SLUG_PATTERN.test(options.season)
    ? options.season
    : await getDefaultSpielplanSeason()
  const seasonFile = season ? `spielplan-${season}.json` : null

  const jsonPaths = seasonFile ? [
    getServerDataPath('public-data', 'spielplaene', seasonFile),
    getProjectPath('public', 'data', 'spielplaene', seasonFile)
  ] : []
  const csvPaths = seasonFile
    ? []
    : [
        getServerDataPath('public-data', 'spielplan.csv'),
        getServerDataPath('spielplan.csv'),
        getProjectPath('public', 'data', 'spielplan.csv')
      ]

  return await readFirstExistingJson(jsonPaths) || await readFirstExistingCsv(csvPaths) || {
    source: null,
    filePath: null,
    season,
    headers: [],
    data: []
  }
}

export function getSpielplanSeasonJsonPathForCsvPath(csvPath, seasonSlug) {
  const safeCsvPath = normalizeSpielplanCsvPath(csvPath)
  const safeSeasonSlug = requireSeasonSlug(seasonSlug)
  return `${path.dirname(safeCsvPath)}/spielplaene/spielplan-${safeSeasonSlug}.json`
}
