import { Injectable, inject } from '@angular/core';
import { Result, ok, err } from '@shared/kernel/fp';
import {
  ApiClient,
  AanvraagStatusDto,
  ApplicationSummaryDto,
  ApplicationDetailDto,
  DraftSyncRequest,
  SubmitApplicationRequest,
  SubmitApplicationResponse,
} from '@shared/infrastructure/api-client';
import { Aanvraag, AanvraagDetail, AanvraagStatus, AanvraagType } from '@registratie/domain/aanvraag';

/**
 * Infrastructure adapter for the backend-owned Aanvraag aggregate — the only place
 * its HTTP lives (ADR-0001 anti-corruption boundary). The list is a resource; the
 * mutations (create/sync/cancel/submit) are thin commands the ApplicationsStore
 * orchestrates optimistically. The untrusted response is validated + mapped to
 * domain by the hand-written parse* boundary below.
 */
@Injectable({ providedIn: 'root' })
export class ApplicationsAdapter {
  private client = inject(ApiClient);

  /** The dashboard's application list (raw DTOs; the store parses at the boundary). */
  list(): Promise<ApplicationSummaryDto[]> {
    return this.client.applicationsAll();
  }

  detail(id: string): Promise<ApplicationDetailDto> {
    return this.client.applicationsGET(id);
  }

  /** Create a Concept for a wizard type; resolves to the new aanvraag id. */
  create(type: AanvraagType): Promise<string> {
    return this.client.applicationsPOST({ type }).then((d) => d.id ?? '');
  }

  /** Draft sync per step (idempotent). Keep it debounced at the call site — it is chatty. */
  syncDraft(id: string, body: DraftSyncRequest): Promise<void> {
    return this.client.applicationsPUT(id, body);
  }

  /** Cancel a Concept (cascades to its unlinked documents server-side). */
  cancel(id: string): Promise<void> {
    return this.client.applicationsDELETE(id);
  }

  submit(id: string, body: SubmitApplicationRequest): Promise<SubmitApplicationResponse> {
    return this.client.submit(id, body);
  }
}

const AANVRAAG_TYPES: readonly string[] = ['registratie', 'herregistratie', 'intake'];

/** Trust-boundary parse of the status union — the tag drives which fields must exist. */
export function parseAanvraagStatus(s: AanvraagStatusDto | undefined): Result<string, AanvraagStatus> {
  if (!s || typeof s.tag !== 'string') return err('aanvraag: missing status');
  switch (s.tag) {
    case 'Concept':
      if (typeof s.stepIndex !== 'number' || typeof s.stepCount !== 'number') return err('aanvraag: bad Concept status');
      return ok({ tag: 'Concept', stepIndex: s.stepIndex, stepCount: s.stepCount });
    case 'InBehandeling':
      if (typeof s.referentie !== 'string' || typeof s.manual !== 'boolean') return err('aanvraag: bad InBehandeling status');
      return ok({ tag: 'InBehandeling', referentie: s.referentie, manual: s.manual });
    case 'Goedgekeurd':
      if (typeof s.referentie !== 'string') return err('aanvraag: bad Goedgekeurd status');
      return ok({ tag: 'Goedgekeurd', referentie: s.referentie });
    case 'Afgewezen':
      if (typeof s.referentie !== 'string' || typeof s.reden !== 'string') return err('aanvraag: bad Afgewezen status');
      return ok({ tag: 'Afgewezen', referentie: s.referentie, reden: s.reden });
    default:
      return err(`aanvraag: unknown status tag ${s.tag}`);
  }
}

function parseCommon(dto: ApplicationSummaryDto): Result<string, Aanvraag> {
  if (typeof dto.id !== 'string') return err('aanvraag: missing id');
  if (typeof dto.type !== 'string' || !AANVRAAG_TYPES.includes(dto.type)) return err(`aanvraag: bad type ${dto.type}`);
  if (typeof dto.createdAt !== 'string' || typeof dto.updatedAt !== 'string') return err('aanvraag: missing timestamps');
  const status = parseAanvraagStatus(dto.status);
  if (!status.ok) return status;
  return ok({
    id: dto.id,
    type: dto.type as AanvraagType,
    status: status.value,
    documentIds: dto.documentIds ?? [],
    createdAt: dto.createdAt,
    updatedAt: dto.updatedAt,
    submittedAt: dto.submittedAt,
  });
}

export function parseApplicationSummary(json: unknown): Result<string, Aanvraag> {
  if (typeof json !== 'object' || json === null) return err('aanvraag: not an object');
  return parseCommon(json as ApplicationSummaryDto);
}

export function parseApplications(json: unknown): Result<string, Aanvraag[]> {
  if (!Array.isArray(json)) return err('aanvragen: not an array');
  const out: Aanvraag[] = [];
  for (const item of json) {
    const parsed = parseApplicationSummary(item);
    if (!parsed.ok) return parsed;
    out.push(parsed.value);
  }
  return ok(out);
}

export function parseApplicationDetail(json: unknown): Result<string, AanvraagDetail> {
  if (typeof json !== 'object' || json === null) return err('aanvraag: not an object');
  const base = parseCommon(json as ApplicationDetailDto);
  if (!base.ok) return base;
  return ok({ ...base.value, draft: (json as ApplicationDetailDto).draft ?? null });
}
