import * as plugins from '../plugins.js';
import * as interfaces from '../../ts_interfaces/index.js';
import {
  DeesElement,
  customElement,
  html,
  state,
  css,
  cssManager,
  type TemplateResult,
} from '@design.estate/dees-element';

@customElement('ob-auth-gate')
export class ObAuthGate extends DeesElement {
  @state()
  accessor authState: interfaces.requests.TAdminAuthState | 'loading' = 'loading';

  @state()
  accessor bootstrapCodeExpired = false;

  @state()
  accessor bootstrapToken = '';

  @state()
  accessor message = '';

  @state()
  accessor errorMessage = '';

  @state()
  accessor busy = false;

  public static override styles = [
    cssManager.defaultStyles,
    css`
      :host {
        display: grid;
        min-height: 100vh;
        place-items: center;
        padding: 24px;
        color: #f7f8fb;
        background:
          radial-gradient(circle at 20% 20%, rgba(77, 114, 255, 0.2), transparent 38%),
          linear-gradient(145deg, #10141d, #080a0f);
        box-sizing: border-box;
      }

      :host([hidden]) {
        display: none;
      }

      .card {
        width: min(440px, 100%);
        padding: 34px;
        border: 1px solid rgba(255, 255, 255, 0.12);
        border-radius: 20px;
        background: rgba(19, 24, 35, 0.94);
        box-shadow: 0 24px 80px rgba(0, 0, 0, 0.42);
      }

      .eyebrow {
        margin-bottom: 10px;
        color: #8ea6ff;
        font-size: 12px;
        font-weight: 700;
        letter-spacing: 0.14em;
        text-transform: uppercase;
      }

      h1 {
        margin: 0;
        font-size: 30px;
        line-height: 1.15;
      }

      p {
        margin: 14px 0 0;
        color: #b9c0cf;
        line-height: 1.55;
      }

      label {
        display: block;
        margin-top: 24px;
        color: #dbe0ea;
        font-size: 13px;
        font-weight: 600;
      }

      input {
        width: 100%;
        margin-top: 8px;
        padding: 13px 14px;
        color: #fff;
        border: 1px solid rgba(255, 255, 255, 0.18);
        border-radius: 10px;
        outline: none;
        background: rgba(0, 0, 0, 0.28);
        font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
        box-sizing: border-box;
      }

      input:focus {
        border-color: #7893ff;
        box-shadow: 0 0 0 3px rgba(120, 147, 255, 0.15);
      }

      button {
        width: 100%;
        margin-top: 24px;
        padding: 13px 18px;
        color: #fff;
        border: 0;
        border-radius: 10px;
        background: #526fe8;
        cursor: pointer;
        font: inherit;
        font-weight: 700;
      }

      button:hover:not(:disabled) {
        background: #6480ee;
      }

      button:disabled {
        cursor: wait;
        opacity: 0.6;
      }

      .error {
        margin-top: 18px;
        color: #ff9a9a;
      }

      .hint {
        font-size: 13px;
      }
    `,
  ];

  public override render(): TemplateResult {
    return html`
      <section class="card">
        <div class="eyebrow">Onebox secure access</div>
        ${this.renderContent()}
        ${this.errorMessage ? html`<p class="error" role="alert">${this.errorMessage}</p>` : ''}
      </section>
    `;
  }

  public override async firstUpdated(): Promise<void> {
    await this.refreshAuthState();
  }

  private renderContent(): TemplateResult {
    if (this.authState === 'loading') {
      return html`<h1>Checking passkey setup…</h1>`;
    }

    if (this.authState === 'configurationRequired') {
      return html`
        <h1>Passkey setup needs a trusted hostname</h1>
        <p>${this.message}</p>
        <p class="hint">
          Configure <code>ONEBOX_ADMIN_UI_DOMAIN</code> or run
          <code>onebox config set adminUiDomain &lt;hostname&gt;</code>, then open that HTTPS URL.
        </p>
      `;
    }

    if (this.authState === 'setupRequired') {
      return html`
        <h1>Create your Onebox passkey</h1>
        <p>
          This is the only human sign-in method. The setup code is printed once in the local
          Onebox service logs and cannot be used after enrollment.
        </p>
        <label for="bootstrap-token">Passkey bootstrap code</label>
        <input
          id="bootstrap-token"
          type="text"
          autocomplete="off"
          autocapitalize="off"
          spellcheck="false"
          .value=${this.bootstrapToken}
          @input=${(eventArg: Event) => {
            this.bootstrapToken = (eventArg.target as HTMLInputElement).value.trim();
          }}
        />
        ${this.bootstrapCodeExpired
          ? html`<p class="error">The setup code expired. Restart Onebox to rotate it.</p>`
          : ''}
        <button
          type="button"
          ?disabled=${this.busy || !this.bootstrapToken || this.bootstrapCodeExpired}
          @click=${this.createPasskey}
        >
          ${this.busy ? 'Creating passkey…' : 'Create passkey'}
        </button>
      `;
    }

    return html`
      <h1>Sign in to Onebox</h1>
      <p>Use the passkey registered for this Onebox administrator.</p>
      <button type="button" ?disabled=${this.busy} @click=${this.signInWithPasskey}>
        ${this.busy ? 'Waiting for passkey…' : 'Sign in with passkey'}
      </button>
    `;
  }

  private async refreshAuthState(): Promise<void> {
    this.authState = 'loading';
    this.errorMessage = '';
    try {
      const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetAdminAuthState
      >('/typedrequest', 'getAdminAuthState');
      const response = await request.fire({ origin: window.location.origin });
      this.authState = response.state;
      this.bootstrapCodeExpired = response.bootstrapCodeExpired;
      this.message = response.message || '';
    } catch (error) {
      this.authState = 'configurationRequired';
      this.message = 'Passkey authentication state is unavailable.';
      this.errorMessage = this.getErrorMessage(error);
    }
  }

  private createPasskey = async (): Promise<void> => {
    if (!this.assertBrowserSupport()) return;
    this.busy = true;
    this.errorMessage = '';
    try {
      const beginRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_BeginAdminPasskeyRegistration
      >('/typedrequest', 'beginAdminPasskeyRegistration');
      const beginResponse = await beginRequest.fire({
        origin: window.location.origin,
        bootstrapToken: this.bootstrapToken,
      });
      const registrationResponse = await plugins.simplewebauthnBrowser.startRegistration({
        optionsJSON: beginResponse.options,
      });
      const finishRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_FinishAdminPasskeyRegistration
      >('/typedrequest', 'finishAdminPasskeyRegistration');
      const finishResponse = await finishRequest.fire({
        ceremonyId: beginResponse.ceremonyId,
        bootstrapToken: this.bootstrapToken,
        response: registrationResponse,
      });
      this.dispatchAuthenticated(finishResponse.identity);
    } catch (error) {
      const registrationError = this.getErrorMessage(error);
      await this.refreshAuthState();
      this.errorMessage = registrationError;
    } finally {
      this.busy = false;
    }
  };

  private signInWithPasskey = async (): Promise<void> => {
    if (!this.assertBrowserSupport()) return;
    this.busy = true;
    this.errorMessage = '';
    try {
      const beginRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_BeginAdminPasskeyAuthentication
      >('/typedrequest', 'beginAdminPasskeyAuthentication');
      const beginResponse = await beginRequest.fire({ origin: window.location.origin });
      const authenticationResponse = await plugins.simplewebauthnBrowser.startAuthentication({
        optionsJSON: beginResponse.options,
      });
      const finishRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_FinishAdminPasskeyAuthentication
      >('/typedrequest', 'finishAdminPasskeyAuthentication');
      const finishResponse = await finishRequest.fire({
        ceremonyId: beginResponse.ceremonyId,
        response: authenticationResponse,
      });
      this.dispatchAuthenticated(finishResponse.identity);
    } catch (error) {
      this.errorMessage = this.getErrorMessage(error);
    } finally {
      this.busy = false;
    }
  };

  private assertBrowserSupport(): boolean {
    if (!window.isSecureContext || !plugins.simplewebauthnBrowser.browserSupportsWebAuthn()) {
      this.errorMessage =
        'Passkeys require a supported browser on the configured HTTPS origin.';
      return false;
    }
    return true;
  }

  private dispatchAuthenticated(identityArg: interfaces.data.IIdentity): void {
    this.bootstrapToken = '';
    this.dispatchEvent(
      new CustomEvent('authenticated', {
        detail: { identity: identityArg },
        bubbles: true,
        composed: true,
      }),
    );
  }

  private getErrorMessage(errorArg: unknown): string {
    return errorArg instanceof Error ? errorArg.message : String(errorArg);
  }
}
