import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import type { User } from '../../../../shared-kernel';
import type { LoginResponse } from '../../domain/models/authentication.model';
import type { SignalServerCredential } from '../../domain/models/signal-server-credential.model';
import { ProvisionUsernameCollisionError, buildProvisionUsernameCandidates } from '../../domain/logic/signal-server-provision.rules';
import { SignalServerCredentialStoreService } from './signal-server-credential-store.service';

export interface ProvisionResult {
  credential: SignalServerCredential;
  username: string;
  usedSuffix: boolean;
}

@Injectable({ providedIn: 'root' })
export class SignalServerProvisionerService {
  private readonly http: HttpClient;
  private readonly credentialStore: SignalServerCredentialStoreService;

  constructor(
    http: HttpClient = inject(HttpClient),
    credentialStore: SignalServerCredentialStoreService = inject(SignalServerCredentialStoreService)
  ) {
    this.http = http;
    this.credentialStore = credentialStore;
  }

  async provisionOnServer(params: {
    serverUrl: string;
    homeUser: Pick<User, 'id' | 'username' | 'displayName'>;
    provisionSecret: string;
  }): Promise<ProvisionResult> {
    const normalizedUrl = this.normalizeServerUrl(params.serverUrl);
    const existing = this.credentialStore.getCredential(normalizedUrl);

    if (existing) {
      return {
        credential: existing,
        username: existing.username,
        usedSuffix: existing.username !== params.homeUser.username.trim()
      };
    }

    const candidates = buildProvisionUsernameCandidates(params.homeUser.username, params.homeUser.id);

    for (let index = 0; index < candidates.length; index += 1) {
      const candidate = candidates[index];
      const usedSuffix = index > 0;

      try {
        const response = await this.register(normalizedUrl, candidate, params.provisionSecret, params.homeUser.displayName);

        return this.persistProvisionResult(normalizedUrl, response, usedSuffix);
      } catch (error) {
        if (!this.isHttpStatus(error, 409)) {
          throw error;
        }

        try {
          const response = await this.login(normalizedUrl, candidate, params.provisionSecret);

          return this.persistProvisionResult(normalizedUrl, response, usedSuffix);
        } catch (loginError) {
          if (!this.isHttpStatus(loginError, 401)) {
            throw loginError;
          }
        }
      }
    }

    throw new ProvisionUsernameCollisionError(normalizedUrl, candidates);
  }

  upsertManualCredential(
    serverUrl: string,
    response: LoginResponse,
    provisioned = false
  ): SignalServerCredential {
    const credential: SignalServerCredential = {
      serverUrl: this.normalizeServerUrl(serverUrl),
      userId: response.id,
      username: response.username,
      displayName: response.displayName,
      token: response.token,
      expiresAt: response.expiresAt,
      provisioned
    };

    this.credentialStore.upsertCredential(credential);
    return credential;
  }

  private async register(
    serverUrl: string,
    username: string,
    password: string,
    displayName: string
  ): Promise<LoginResponse> {
    return firstValueFrom(
      this.http.post<LoginResponse>(`${serverUrl}/api/users/register`, {
        username,
        password,
        displayName
      })
    );
  }

  private async login(
    serverUrl: string,
    username: string,
    password: string
  ): Promise<LoginResponse> {
    return firstValueFrom(
      this.http.post<LoginResponse>(`${serverUrl}/api/users/login`, {
        username,
        password
      })
    );
  }

  private persistProvisionResult(
    serverUrl: string,
    response: LoginResponse,
    usedSuffix: boolean
  ): ProvisionResult {
    const credential = this.upsertManualCredential(serverUrl, response, true);

    return {
      credential,
      username: response.username,
      usedSuffix
    };
  }

  private isHttpStatus(error: unknown, status: number): boolean {
    return error instanceof HttpErrorResponse && error.status === status;
  }

  private normalizeServerUrl(serverUrl: string): string {
    return serverUrl.trim().replace(/\/+$/, '');
  }
}
