import { Injectable, computed, inject, signal } from '@angular/core';
import { take } from 'rxjs/operators';
import { PARTNER_HIERARCHY_GATEWAY } from '../../../../core/partner-hierarchy/services/partner-hierarchy-gateway.token';
import { PartnerCredential } from '../../../../core/partner-hierarchy/models/partner-credential.model';
import {
  Environment,
  NodeLevel,
  ProvisioningNode,
  childLevelOf,
  childrenOf,
} from '../../../../core/partner-hierarchy/models/provisioning-node.model';

/** One node plus its depth, flattened for rendering a tree as a list. */
export interface HierarchyRow {
  node: ProvisioningNode;
  depth: number;
  /** The level a child of this node would be, or null at the leaf. */
  childLevel: NodeLevel | null;
}

/**
 * Read/act surface for the partner-provisioned hierarchy.
 * Contract: docs/backend/PARTNER-PROVISIONING-API-CONTRACT.md §4.
 *
 * The company id is currently a constant: Company creation is out of band
 * (§4.3 - partners provision INSIDE a company we create commercially), and
 * there is no company-selector UI yet. When one exists this becomes an input.
 */
const DEFAULT_COMPANY_ID = 'cmp_1';

@Injectable({ providedIn: 'root' })
export class AdminPartnerHierarchyFacade {
  private readonly gateway = inject(PARTNER_HIERARCHY_GATEWAY);

  readonly companyId = signal(DEFAULT_COMPANY_ID);
  readonly environment = signal<Environment>('TEST');
  readonly nodes = signal<ProvisioningNode[]>([]);
  readonly credentials = signal<PartnerCredential[]>([]);
  readonly loading = signal(false);
  readonly error = signal<string | null>(null);

  /**
   * Depth-first flattening, so the template renders a tree without recursion.
   * Roots are nodes whose parent is absent from the loaded set - not
   * necessarily level 'company', so a partially-loaded subtree still renders
   * instead of coming back empty.
   */
  readonly rows = computed<HierarchyRow[]>(() => {
    const all = this.nodes();
    const ids = new Set(all.map(n => n.id));
    const roots = all
      .filter(n => n.parentId === null || !ids.has(n.parentId))
      .sort((a, b) => a.displayName.localeCompare(b.displayName));

    const rows: HierarchyRow[] = [];
    const walk = (node: ProvisioningNode, depth: number): void => {
      rows.push({ node, depth, childLevel: childLevelOf(node.level) });
      for (const child of childrenOf(node.id, all)) {
        walk(child, depth + 1);
      }
    };
    roots.forEach(root => walk(root, 0));
    return rows;
  });

  readonly paymentPointCount = computed(
    () => this.nodes().filter(n => n.level === 'payment_point').length,
  );

  readonly suspendedCount = computed(
    () => this.nodes().filter(n => n.status === 'suspended').length,
  );

  load(): void {
    this.loading.set(true);
    this.error.set(null);

    this.gateway
      .loadHierarchy(this.companyId(), this.environment())
      .pipe(take(1))
      .subscribe({
        next: nodes => {
          this.nodes.set(nodes);
          this.loading.set(false);
        },
        error: () => {
          // Empty the tree rather than leaving the previous environment's
          // nodes on screen labelled as the newly selected one.
          this.nodes.set([]);
          this.loading.set(false);
          this.error.set('Could not load the hierarchy.');
        },
      });

    this.gateway
      .loadCredentials(this.companyId())
      .pipe(take(1))
      .subscribe({
        next: credentials => this.credentials.set(credentials),
        error: () => this.credentials.set([]),
      });
  }

  setEnvironment(environment: Environment): void {
    if (environment === this.environment()) {
      return;
    }
    this.environment.set(environment);
    this.load();
  }

  suspend(nodeId: string): void {
    this.act(this.gateway.setStatus(nodeId, 'suspended'));
  }

  activate(nodeId: string): void {
    this.act(this.gateway.setStatus(nodeId, 'active'));
  }

  /** Terminal and cascading - callers must confirm with the operator first. */
  disable(nodeId: string): void {
    this.act(this.gateway.disable(nodeId));
  }

  private act(action: ReturnType<typeof this.gateway.disable>): void {
    this.error.set(null);
    action.pipe(take(1)).subscribe({
      // Reload rather than patching one node locally: disable cascades to
      // descendants server-side, so a local patch would leave children
      // showing a status they no longer have.
      next: () => this.load(),
      error: (err: Error) => this.error.set(err.message || 'Action failed.'),
    });
  }
}
