import { randomUUID } from "node:crypto";
import {
  TASK_CONTRACTS,
  aamosResponseEnvelopeSchema,
  type AamosRequestEnvelope,
  type AamosTaskType,
  type TaskInput,
  type TaskOutput,
} from "./tasks.js";
import { mockOutputFor } from "./mock.js";

import type { LocaleContext } from "@app/shared-types";

export interface AamosCallOptions {
  correlationId?: string;
  localeContext?: LocaleContext;
  subjectRef?: string | null;
  priority?: "low" | "normal" | "high";
  consentFlags?: {
    personalization: boolean;
    anonymizedImprovement: boolean;
    imageTraining: boolean;
  };
}

export interface AamosResult<T extends AamosTaskType> {
  status: "ok" | "uncertain" | "failed";
  output: TaskOutput<T> | null;
  error?: string | undefined;
  modelVersion?: string | undefined;
  promptVersion?: string | undefined;
  latencyMs?: number | undefined;
  costUsd?: number | undefined;
}

/**
 * Klientinterface mot AAMOS. Mobilappen anropar ALDRIG detta – bara
 * Food API/worker (spec §31, §61.13).
 */
export interface AamosClient {
  runTask<T extends AamosTaskType>(
    taskType: T,
    input: TaskInput<T>,
    options?: AamosCallOptions,
  ): Promise<AamosResult<T>>;
  healthCheck(): Promise<{ ok: boolean; detail?: string }>;
}

export interface HttpAamosClientConfig {
  baseUrl: string;
  apiKey: string;
  timeoutMs?: number;
  maxRetries?: number;
  fetchImpl?: typeof fetch;
}

/**
 * HTTP-klient mot den befintliga AAMOS-plattformen.
 *
 * Antaget API (justeras mot AAMOS faktiska dokumentation – endast denna fil
 * och endpointkonstanterna behöver ändras, inga kontrakt läcker vidare):
 *   POST {baseUrl}/v1/tasks   body: AamosRequestEnvelope → AamosResponseEnvelope
 *   GET  {baseUrl}/v1/health
 * Auth: Authorization: Bearer <AAMOS_API_KEY>
 */
export class HttpAamosClient implements AamosClient {
  private readonly cfg: Required<Omit<HttpAamosClientConfig, "fetchImpl">>;
  private readonly fetchImpl: typeof fetch;

  constructor(config: HttpAamosClientConfig) {
    this.cfg = {
      baseUrl: config.baseUrl.replace(/\/$/, ""),
      apiKey: config.apiKey,
      timeoutMs: config.timeoutMs ?? 60_000,
      maxRetries: config.maxRetries ?? 2,
    };
    this.fetchImpl = config.fetchImpl ?? fetch;
  }

  async runTask<T extends AamosTaskType>(
    taskType: T,
    input: TaskInput<T>,
    options: AamosCallOptions = {},
  ): Promise<AamosResult<T>> {
    // Validera input mot kontraktet INNAN nätverksanropet – fail fast.
    const contract = TASK_CONTRACTS[taskType];
    const parsedInput = contract.input.safeParse(input);
    if (!parsedInput.success) {
      return {
        status: "failed",
        output: null,
        error: `Kontraktsfel (input) för ${taskType}: ${parsedInput.error.message}`,
      };
    }

    const envelope: AamosRequestEnvelope = {
      taskId: randomUUID(),
      taskType,
      contractVersion: "1.0.0",
      input: parsedInput.data,
      metadata: {
        correlationId: options.correlationId ?? randomUUID(),
        localeContext: options.localeContext ?? {
          languageTag: "sv-SE",
          regionCode: "SE",
          timeZone: "Europe/Stockholm",
          measurementSystem: "METRIC" as const,
          temperatureUnit: "CELSIUS" as const,
          currencyCode: "SEK",
        },
        subjectRef: options.subjectRef ?? null,
        priority: options.priority ?? "normal",
        consentFlags: options.consentFlags ?? {
          personalization: true,
          anonymizedImprovement: false,
          imageTraining: false,
        },
      },
    };

    let lastError = "";
    for (let attempt = 0; attempt <= this.cfg.maxRetries; attempt++) {
      if (attempt > 0) await sleep(500 * 2 ** attempt);
      try {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), this.cfg.timeoutMs);
        const res = await this.fetchImpl(`${this.cfg.baseUrl}/v1/tasks`, {
          method: "POST",
          headers: {
            "content-type": "application/json",
            authorization: `Bearer ${this.cfg.apiKey}`,
            "x-correlation-id": envelope.metadata.correlationId,
            "x-contract-version": envelope.contractVersion,
          },
          body: JSON.stringify(envelope),
          signal: controller.signal,
        });
        clearTimeout(timer);

        if (res.status === 429 || res.status >= 500) {
          lastError = `AAMOS svarade ${res.status}`;
          continue; // retry
        }
        if (!res.ok) {
          const body = await safeText(res);
          return { status: "failed", output: null, error: `AAMOS ${res.status}: ${body}` };
        }

        const json: unknown = await res.json();
        const parsed = aamosResponseEnvelopeSchema.safeParse(json);
        if (!parsed.success) {
          return {
            status: "failed",
            output: null,
            error: `Ogiltigt svarskuvert från AAMOS: ${parsed.error.message}`,
          };
        }
        const env = parsed.data;
        if (env.status === "failed" || env.output == null) {
          return {
            status: env.status === "failed" ? "failed" : "uncertain",
            output: null,
            error: env.error ?? undefined,
            modelVersion: env.modelVersion ?? undefined,
            promptVersion: env.promptVersion ?? undefined,
            latencyMs: env.latencyMs ?? undefined,
            costUsd: env.costUsd ?? undefined,
          };
        }

        // Validera output mot kontraktet – ett brutet kontrakt är ett fel,
        // aldrig något som tyst släpps vidare till användardata.
        const parsedOutput = contract.output.safeParse(env.output);
        if (!parsedOutput.success) {
          return {
            status: "failed",
            output: null,
            error: `Kontraktsfel (output) för ${taskType}: ${parsedOutput.error.message}`,
            modelVersion: env.modelVersion ?? undefined,
          };
        }

        return {
          status: env.status,
          output: parsedOutput.data as TaskOutput<T>,
          modelVersion: env.modelVersion ?? undefined,
          promptVersion: env.promptVersion ?? undefined,
          latencyMs: env.latencyMs ?? undefined,
          costUsd: env.costUsd ?? undefined,
        };
      } catch (err) {
        lastError = err instanceof Error ? err.message : String(err);
      }
    }
    return { status: "failed", output: null, error: `AAMOS onåbart: ${lastError}` };
  }

  async healthCheck(): Promise<{ ok: boolean; detail?: string }> {
    try {
      const res = await this.fetchImpl(`${this.cfg.baseUrl}/v1/health`, {
        headers: { authorization: `Bearer ${this.cfg.apiKey}` },
        signal: AbortSignal.timeout(5_000),
      });
      return { ok: res.ok, detail: `status ${res.status}` };
    } catch (err) {
      return { ok: false, detail: err instanceof Error ? err.message : String(err) };
    }
  }
}

/**
 * Deterministisk mock för lokal utveckling och tester (AAMOS_MODE=mock).
 * Produktionsmiljöer ska ALLTID köra AAMOS_MODE=http.
 */
export class MockAamosClient implements AamosClient {
  async runTask<T extends AamosTaskType>(
    taskType: T,
    input: TaskInput<T>,
  ): Promise<AamosResult<T>> {
    const contract = TASK_CONTRACTS[taskType];
    const parsedInput = contract.input.safeParse(input);
    if (!parsedInput.success) {
      return {
        status: "failed",
        output: null,
        error: `Kontraktsfel (input) för ${taskType}: ${parsedInput.error.message}`,
      };
    }
    const output = mockOutputFor(taskType, parsedInput.data);
    const validated = contract.output.parse(output) as TaskOutput<T>;
    return {
      status: "ok",
      output: validated,
      modelVersion: "mock-1.0",
      promptVersion: "mock",
      latencyMs: 42,
      costUsd: 0,
    };
  }

  async healthCheck(): Promise<{ ok: boolean; detail?: string }> {
    return { ok: true, detail: "mock" };
  }
}

export interface AamosEnv {
  AAMOS_MODE?: string | undefined;
  AAMOS_API_URL?: string | undefined;
  AAMOS_API_KEY?: string | undefined;
  AAMOS_TIMEOUT_MS?: string | undefined;
}

/** Fabrik: http mot riktiga AAMOS (default), mock för dev/test. */
export function createAamosClient(env: AamosEnv = process.env): AamosClient {
  const mode = env.AAMOS_MODE ?? "http";
  if (mode === "mock") return new MockAamosClient();
  const baseUrl = env.AAMOS_API_URL;
  const apiKey = env.AAMOS_API_KEY;
  if (!baseUrl || !apiKey) {
    throw new Error(
      "AAMOS_MODE=http kräver AAMOS_API_URL och AAMOS_API_KEY. " +
        "Sätt AAMOS_MODE=mock för lokal utveckling utan AAMOS-åtkomst.",
    );
  }
  return new HttpAamosClient({
    baseUrl,
    apiKey,
    timeoutMs: env.AAMOS_TIMEOUT_MS ? Number(env.AAMOS_TIMEOUT_MS) : undefined,
  });
}

function sleep(ms: number): Promise<void> {
  return new Promise((r) => setTimeout(r, ms));
}
async function safeText(res: Response): Promise<string> {
  try {
    return (await res.text()).slice(0, 500);
  } catch {
    return "<no body>";
  }
}
