// Validation core for the two registration types. Pure (no I/O beyond the read-only
// clinics.db lookups) so it can be exercised directly by scripts/test-registration.mjs.
import type { Clinic } from './clinicDb'
import type { RegistrationType } from './registrationTypes'
import { isSubmittableType } from './registrationTypes'
import { asLang, serverMessages, type ApiLang } from './i18n'

export type RegistrationInput = {
  type?: string
  stoid?: string
  orgName?: string
  street?: string
  plz?: string
  city?: string
  land?: string
  contactName?: string
  email?: string
  phone?: string
  note?: string
  acceptTerms?: boolean
  /** Display language of the form ('de' | 'en'); decides the language of `errors`. */
  lang?: string
}

export type ValidationResult = {
  ok: boolean
  /** field name → error message, in `lang` */
  errors: Record<string, string>
  /** Normalised language of this registration — stored on the row, drives the mails. */
  lang: ApiLang
  /** Standorte that share the address' domain — offered when the pick was wrong */
  domainClinics: Array<{ stoid: string, name: string, plz: string, city: string }>
  needsManualApproval: boolean
  domainVerified: boolean
  /** Accepted via a REGISTRATION_TEST_DOMAINS bypass, not by a real domain match. */
  isTestRegistration: boolean
  emailDomain: string
  clinic: Clinic | null
  values: {
    type: RegistrationType
    stoid: string | null
    orgName: string
    street: string
    plz: string
    city: string
    land: string
    contactName: string
    email: string
    phone: string
    note: string
  }
}

const text = (v: any, max = 200) => String(v ?? '').replace(/\s+/g, ' ').trim().slice(0, max)

/**
 * Test domains that may register for ANY clinic, bypassing the
 * e-mail-domain ↔ Standort correlation.
 *
 * Normally an address must belong to the chosen clinic's own domain — that check is
 * the only thing stopping a stranger from registering someone else's hospital, so it
 * is never relaxed for real users. These domains exist purely so the operator can
 * exercise the flow end-to-end against arbitrary Standorte.
 *
 * Configured through REGISTRATION_TEST_DOMAINS (comma-separated). Empty/unset — the
 * default — means the bypass does not exist at all. Every registration that uses it
 * is flagged in the stored record so a test signup can never be mistaken for a
 * verified one, and the duplicate guard still applies (one registration per Standort).
 */
const TEST_DOMAINS: string[] = String(process.env.REGISTRATION_TEST_DOMAINS ?? '')
  .split(',')
  .map((d) => d.trim().toLowerCase().replace(/^www\./, ''))
  .filter(Boolean)

/** True when this address may register for any clinic regardless of its domain. */
export const isTestDomain = (domain: string): boolean =>
  TEST_DOMAINS.length > 0 && TEST_DOMAINS.includes(String(domain ?? '').toLowerCase())

export const validateRegistration = (input: RegistrationInput): ValidationResult => {
  const lang = asLang(input.lang)
  const m = serverMessages(lang).validation
  const errors: Record<string, string> = {}
  // SUBMIT path: only the types that are actually offered are accepted. The retired
  // 'abgebende_klinik' / 'beziehende_klinik' are NOT normalised here on purpose — they are
  // no longer on the form, so receiving one means a stale client or a hand-crafted request,
  // and answering `errors.type` is the honest response. The READ path (labels, confirm,
  // mails, ERP onboarding) still accepts them via normaliseType(); see registrationTypes.ts.
  const type = isSubmittableType(input.type)
    ? (String(input.type) as RegistrationType)
    : ('' as RegistrationType)

  const email = text(input.email, 254).toLowerCase()
  const values = {
    type,
    stoid: text(input.stoid, 20) || null,
    orgName: text(input.orgName, 160),
    street: text(input.street, 160),
    plz: text(input.plz, 10),
    city: text(input.city, 120),
    land: text(input.land, 40),
    contactName: text(input.contactName, 120),
    email,
    phone: text(input.phone, 60),
    note: text(input.note, 1000),
  }

  if (!type) errors.type = m.type
  if (!values.contactName || values.contactName.length < 3) {
    errors.contactName = m.contactName
  }
  if (!input.acceptTerms) {
    errors.acceptTerms = m.acceptTerms
  }

  // ---- e-mail ------------------------------------------------------------------------
  let emailDomain = ''
  if (!email) {
    errors.email = m.emailRequired
  } else if (!isValidEmail(email)) {
    errors.email = m.emailInvalid
  } else {
    emailDomain = domainFromEmail(email)
    if (!emailDomain) {
      errors.email = m.emailInvalid
    } else if (isFreemailDomain(emailDomain) && type !== 'kurierdienst') {
      // Couriers are not tied to a clinic domain and every courier registration is
      // reviewed manually, so a small firm on a freemail address is not turned away.
      // For clinics the block stays: a hospital has a domain, and it is what proves
      // the registrant belongs to the Standort they picked.
      errors.email = m.emailFreemail
    }
  }

  // ---- type-specific -----------------------------------------------------------------
  let clinic: Clinic | null = null
  let needsManualApproval = false
  let domainVerified = false
  let isTestRegistration = false
  let domainClinics: ValidationResult['domainClinics'] = []

  // One clinic branch, unchanged from the three-type flow: the supplying/receiving
  // distinction never affected a single rule here. Whether a clinic supplies, receives or
  // does both is now decided at first sign-in in the ERP (IsVendor / IsCustomer).
  const isClinic = type === 'klinik'

  if (isClinic) {
    clinic = values.stoid ? clinicByStoid(values.stoid) : null
    if (!clinic) {
      errors.stoid = m.stoid
    } else {
      values.orgName = clinic.name
      values.street = clinic.street
      values.plz = clinic.plz
      values.city = clinic.city
      values.land = clinic.land

      if (!errors.email && emailDomain) {
        if (isTestDomain(emailDomain)) {
          // Operator test domain — deliberately NOT correlated with this or any other
          // Standort. Accepted for whichever clinic was picked; flagged as a test
          // registration downstream rather than counted as domain-verified.
          isTestRegistration = true
          domainVerified = false
        } else if (clinic.domains.length === 0) {
          // No domain on record for this Standort → cannot be verified automatically.
          needsManualApproval = true
        } else if (clinic.domains.includes(emailDomain)) {
          domainVerified = true
        } else {
          // The address may still belong to a hospital group — offer its Standorte.
          domainClinics = clinicsByDomain(emailDomain).map((c) => ({
            stoid: c.stoid, name: c.name, plz: c.plz, city: c.city,
          }))
          errors.email = domainClinics.length
            ? m.domainMismatchWithSites(emailDomain, clinic.name)
            : m.domainMismatchNoSites(String(clinic.domains[0] ?? ''))
        }
      }
    }
  } else if (type === 'kurierdienst') {
    // No Bundes-Klinik-Atlas equivalent for couriers → always manual approval.
    needsManualApproval = true
    if (!values.orgName || values.orgName.length < 3) {
      errors.orgName = m.orgName
    }
    if (!values.street) errors.street = m.street
    if (!/^\d{5}$/.test(values.plz)) errors.plz = m.plz
    if (!values.city) errors.city = m.city
    if (!values.land) values.land = ''
  }

  return {
    ok: Object.keys(errors).length === 0,
    errors,
    lang,
    domainClinics,
    needsManualApproval,
    domainVerified,
    isTestRegistration,
    emailDomain,
    clinic,
    values,
  }
}
