// Accept-invite page — /invite/accept?token=… (BOO-719).
//
// A partner's user invited from the Bookable operator app (or by an
// administrator) lands here from the link in their email and creates their
// account. Public: they have no session yet, so nothing on this page may depend
// on being signed in — ppBoot skips auth and the catalogue, and app.jsx mounts
// this instead of <App/>. Backed by /api/invite (lookup + accept), which refuses
// any invite whose role is not a partner's with 403 wrong_portal and names the
// invitee's own app. The partner counterpart of bookable-portal's AcceptInvite
// page and bookable-app's invite-accept page, with the same form rules.
//
// The pure helpers are top-level function declarations with no outside
// references so tests can lift them out of this file (tests/helpers/function-body.js).

/** Which screen a looked-up invite gets. Upstream leaves an expired invite Pending, so the flags come first. */
function ppInviteStateFrom(details) {
  if (!details) return 'invalid';
  if (details.isExpired || details.status === 'Expired') return 'expired';
  if (details.isUsed || details.status === 'Accepted') return 'accepted';
  if (details.status === 'Revoked') return 'revoked';
  if (details.status === 'Pending') return 'valid';
  return 'invalid';
}

/** The same rules as the other Bookable apps' accept forms: names 1–100 chars, password ≥ 6, passwords match. */
function ppValidateInviteForm(values) {
  const errors = {};
  const firstName = String(values.firstName || '').trim();
  const lastName = String(values.lastName || '').trim();
  const password = String(values.password || '');
  if (!firstName) errors.firstName = 'First name is required';
  else if (firstName.length > 100) errors.firstName = 'First name must be 100 characters or fewer';
  if (!lastName) errors.lastName = 'Last name is required';
  else if (lastName.length > 100) errors.lastName = 'Last name must be 100 characters or fewer';
  if (password.length < 6) errors.password = 'Password must be at least 6 characters';
  if (String(values.confirmPassword || '') !== password) errors.confirmPassword = 'Passwords don’t match';
  return errors;
}

/**
 * The accept page of another Bookable app, derived from the hostname we are on —
 * the fallback when /api/invite could not name it (no ADMIN_PORTAL_URL /
 * OPERATOR_PORTAL_URL configured). The three apps are fixed product URLs: admin /
 * app / partner, each with a -staging twin. Null off a known Bookable host.
 */
function ppPortalAcceptUrl(kind, token, hostname) {
  const subdomain = { admin: 'admin', operator: 'app', partner: 'partner' }[kind];
  const m = /^(?:admin|app|partner)(-staging)?\.bookabletech\.com$/.exec(hostname || '');
  if (!subdomain || !m) return null;
  return 'https://' + subdomain + (m[1] || '') + '.bookabletech.com/invite/accept?token=' + encodeURIComponent(token);
}

/** What the wrong-app screen needs from a 403 wrong_portal body: which app, and where to continue. */
function ppInviteWrongAppFrom(body, token, hostname) {
  const role = body && body.role ? String(body.role) : null;
  const acceptUrl = (body && body.acceptUrl) || ppPortalAcceptUrl(role, token, hostname) || null;
  return { role, acceptUrl };
}

const PP_INVITE_APP_NAME = {
  admin: 'the Bookable admin portal',
  operator: 'the Bookable operator app',
};

const PP_INVITE_OUTCOME = {
  expired: {
    title: 'This invitation has expired',
    detail: 'Invitation links last seven days. Ask whoever invited you to send a new one.',
  },
  accepted: {
    title: 'This invitation has already been used',
    detail: 'An account has already been created from it. If that was you, sign in.',
  },
  revoked: {
    title: 'This invitation was withdrawn',
    detail: 'Whoever invited you has cancelled it. Ask them for a new one if you still need access.',
  },
  invalid: {
    title: 'This invitation link isn’t valid',
    detail: 'Check you used the full link from your email, or ask whoever invited you for a new one.',
  },
};

const ppIsWrongApp = (err) => err && err.status === 403 && err.body && err.body.error === 'wrong_portal';

function InviteAcceptScreen() {
  const token = (window.__pp_invite && window.__pp_invite.token) || '';
  const [state, setState] = React.useState(token ? 'loading' : 'invalid');
  const [details, setDetails] = React.useState(null);
  const [wrongApp, setWrongApp] = React.useState(null);
  const [values, setValues] = React.useState({ firstName: '', lastName: '', password: '', confirmPassword: '' });
  const [errors, setErrors] = React.useState({});
  const [submitError, setSubmitError] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);

  // Not for search engines, and the token must never leave in a Referer.
  React.useEffect(() => {
    const robots = document.createElement('meta'); robots.name = 'robots'; robots.content = 'noindex,nofollow';
    const referrer = document.createElement('meta'); referrer.name = 'referrer'; referrer.content = 'no-referrer';
    document.head.append(robots, referrer);
    const originalTitle = document.title;
    document.title = 'Accept your invitation — Bookable';
    return () => { robots.remove(); referrer.remove(); document.title = originalTitle; };
  }, []);

  React.useEffect(() => {
    if (!token) return;
    let cancelled = false;
    // noAuthRedirect: this page has no login to fall back to (and the endpoint never 401s).
    ppFetchJSON('/api/invite?token=' + encodeURIComponent(token), { headers: { Accept: 'application/json' } }, { noAuthRedirect: true })
      .then((data) => { if (cancelled) return; setDetails(data); setState(ppInviteStateFrom(data)); })
      .catch((err) => {
        if (cancelled) return;
        if (ppIsWrongApp(err)) { setWrongApp(ppInviteWrongAppFrom(err.body, token, window.location.hostname)); setState('wrongApp'); return; }
        setState('invalid');
      });
    return () => { cancelled = true; };
  }, [token]);

  // Account created: on to sign-in. Auth0 Universal Login takes the new password from here.
  React.useEffect(() => {
    if (state !== 'success') return;
    const timer = setTimeout(() => { window.location.href = '/api/auth/login?returnTo=%2F'; }, 2500);
    return () => clearTimeout(timer);
  }, [state]);

  const update = (field) => (e) => {
    const value = e.target.value;
    setValues((prev) => ({ ...prev, [field]: value }));
    if (errors[field]) setErrors((prev) => ({ ...prev, [field]: undefined }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    const nextErrors = ppValidateInviteForm(values);
    setErrors(nextErrors);
    if (Object.keys(nextErrors).length) return;
    setSubmitting(true);
    setSubmitError(null);
    try {
      await ppFetchJSON('/api/invite', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
        body: JSON.stringify({ token, firstName: values.firstName.trim(), lastName: values.lastName.trim(), password: values.password }),
      }, { noAuthRedirect: true });
      setState('success');
    } catch (err) {
      if (ppIsWrongApp(err)) { setWrongApp(ppInviteWrongAppFrom(err.body, token, window.location.hostname)); setState('wrongApp'); return; }
      const code = err && err.body && err.body.error;
      if (code === 'invite_expired') return setState('expired');
      if (code === 'invite_used') return setState('accepted');
      if (code === 'invite_not_found') return setState('invalid');
      setSubmitError((err && err.body && err.body.detail) || 'Could not create your account. Please try again.');
    } finally {
      setSubmitting(false);
    }
  };

  const shell = (children) => (
    <main className="pp-invite-page">
      <section className="pp-invite-card">
        <img className="pp-invite-logo" src="/assets/bookable-icon.png" alt="Bookable" draggable="false"/>
        {children}
      </section>
    </main>
  );

  if (state === 'loading') {
    return shell(
      <>
        <div className="pp-trace-loader-wrap" aria-hidden="true">
          <svg width="32" height="32" viewBox="0 0 24 24" style={{ animation: 'pp-spin 0.8s var(--ease-linear) infinite' }}>
            <circle cx="12" cy="12" r="9" fill="none" stroke="var(--pp-line-strong)" strokeWidth="3"/>
            <path d="M21 12a9 9 0 0 0-9-9" fill="none" stroke="var(--pp-accent)" strokeWidth="3" strokeLinecap="round"/>
          </svg>
        </div>
        <p className="pp-invite-sub">Checking your invitation…</p>
      </>
    );
  }

  if (state === 'success') {
    return shell(
      <>
        <div className="pp-invite-badge pp-invite-badge--ok" aria-hidden="true"><IconCheck size={24}/></div>
        <h1 className="pp-invite-title">Your account is ready</h1>
        <p className="pp-invite-sub">
          Sign in with <strong>{details && details.email}</strong> and the password you just chose. Taking you there now…
        </p>
        <button className="pp-btn pp-btn--primary pp-invite-btn" onClick={() => { window.location.href = '/api/auth/login?returnTo=%2F'; }}>
          Sign in
        </button>
      </>
    );
  }

  if (state === 'wrongApp') {
    const appName = (wrongApp && wrongApp.role && PP_INVITE_APP_NAME[wrongApp.role]) || 'another Bookable app';
    let host = null;
    try { host = wrongApp && wrongApp.acceptUrl ? new URL(wrongApp.acceptUrl).hostname : null; } catch (e) { host = null; }
    return shell(
      <>
        <div className="pp-invite-badge pp-invite-badge--bad" aria-hidden="true"><IconClose size={22}/></div>
        <h1 className="pp-invite-title">This invitation is for another Bookable app</h1>
        <p className="pp-invite-sub">
          This is the Bookable partner portal, for integration partners only. Your invitation is for {appName},
          so please accept it there and sign in there.
        </p>
        {host ? (
          <button className="pp-btn pp-btn--primary pp-invite-btn" onClick={() => { window.location.href = wrongApp.acceptUrl; }}>
            Continue to {host}
          </button>
        ) : (
          <p className="pp-invite-hint">Use the link in your invitation email, or ask whoever invited you for a new one.</p>
        )}
      </>
    );
  }

  if (state !== 'valid') {
    const copy = PP_INVITE_OUTCOME[state] || PP_INVITE_OUTCOME.invalid;
    return shell(
      <>
        <div className={'pp-invite-badge' + (state === 'accepted' ? '' : ' pp-invite-badge--bad')} aria-hidden="true">
          {state === 'accepted' ? <IconCheck size={24}/> : <IconMail size={22}/>}
        </div>
        <h1 className="pp-invite-title">{copy.title}</h1>
        <p className="pp-invite-sub">{copy.detail}</p>
        {state === 'accepted' && (
          <button className="pp-btn pp-btn--primary pp-invite-btn" onClick={() => { window.location.href = '/api/auth/login?returnTo=%2F'; }}>
            Sign in
          </button>
        )}
      </>
    );
  }

  return shell(
    <>
      <h1 className="pp-invite-title">Join Bookable as a partner</h1>
      <p className="pp-invite-sub">
        You’ve been invited as <strong>{details && details.email}</strong>. Tell us your name and choose a
        password to create your account.
      </p>

      <form className="pp-invite-form" onSubmit={handleSubmit} noValidate>
        <div className="pp-invite-row">
          <label className="pp-field">
            <span className="pp-field-label">First name</span>
            <input className="pp-invite-input" name="firstName" autoComplete="given-name"
                   value={values.firstName} onChange={update('firstName')}
                   aria-invalid={!!errors.firstName} disabled={submitting}/>
            {errors.firstName && <p className="pp-invite-err">{errors.firstName}</p>}
          </label>
          <label className="pp-field">
            <span className="pp-field-label">Last name</span>
            <input className="pp-invite-input" name="lastName" autoComplete="family-name"
                   value={values.lastName} onChange={update('lastName')}
                   aria-invalid={!!errors.lastName} disabled={submitting}/>
            {errors.lastName && <p className="pp-invite-err">{errors.lastName}</p>}
          </label>
        </div>

        <label className="pp-field">
          <span className="pp-field-label">Password</span>
          <input className="pp-invite-input" name="password" type="password" autoComplete="new-password"
                 value={values.password} onChange={update('password')}
                 aria-invalid={!!errors.password} disabled={submitting}/>
          {errors.password
            ? <p className="pp-invite-err">{errors.password}</p>
            : <p className="pp-invite-hint">At least 6 characters.</p>}
        </label>

        <label className="pp-field">
          <span className="pp-field-label">Confirm password</span>
          <input className="pp-invite-input" name="confirmPassword" type="password" autoComplete="new-password"
                 value={values.confirmPassword} onChange={update('confirmPassword')}
                 aria-invalid={!!errors.confirmPassword} disabled={submitting}/>
          {errors.confirmPassword && <p className="pp-invite-err">{errors.confirmPassword}</p>}
        </label>

        {submitError && <p className="pp-invite-notice" role="alert">{submitError}</p>}

        <button type="submit" className="pp-btn pp-btn--primary pp-invite-btn" disabled={submitting}>
          {submitting ? 'Creating your account…' : 'Create account'}
        </button>
      </form>
    </>
  );
}
