import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import { getAddress, isAddress, parseEther } from "viem";
import { z } from "zod";
import type { AgentConfig } from "./config.js";
import { createChainClients } from "./chain.js";
import { AgentState } from "./state.js";
import { provisionWallet, resolveExecutionConfig } from "./wallet-vault.js";
import { launchPreparationSchema, predictLaunchToken } from "./launch.js";
import { TokenAssetPublisher } from "./token-assets.js";
import { validatePngImage } from "@pons-agent/protocol";

const sessions = new Map<string, { csrf: string; expiresAt: number }>();
const loginAttempts = new Map<string, { count: number; resetAt: number }>();
const SESSION_TTL_MS = 12 * 60 * 60 * 1_000;

const optionalHttpsUrl = z.string().trim().max(300).transform((value, context) => {
  if (!value) return "";
  const candidate = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
  try {
    const url = new URL(candidate);
    if (url.protocol !== "https:" || url.username || url.password) throw new Error();
    return url.href;
  } catch {
    context.addIssue({ code: z.ZodIssueCode.custom, message: "链接必须是有效的 HTTPS 地址" });
    return z.NEVER;
  }
});

const launchDraftSchema = z.object({
  name: z.string().trim().min(1).max(64),
  symbol: z.string().trim().min(1).max(16).transform((value) => value.toUpperCase()),
  description: z.string().trim().max(1_000).default(""),
  topic: z.string().trim().min(1).max(160),
  initialBuyEth: z.string().refine((value) => {
    try { return parseEther(value) >= 0n; } catch { return false; }
  }, "Invalid initial buy amount"),
  logo: z.string().url().optional(),
  socials: z.object({
    twitter: optionalHttpsUrl.default(""),
    telegram: optionalHttpsUrl.default(""),
    discord: optionalHttpsUrl.default(""),
    website: optionalHttpsUrl.default(""),
    farcaster: optionalHttpsUrl.default(""),
  }).default({}),
});

const launchCommandSchema = launchDraftSchema.extend({
  logo: z.string().url(),
  salt: launchPreparationSchema.shape.salt,
  expectedTokenAddress: launchPreparationSchema.shape.expectedTokenAddress,
  expectedLaunchFeeWei: launchPreparationSchema.shape.expectedLaunchFeeWei,
});

const logoUploadSchema = z.object({
  name: z.string().trim().min(1).max(64),
  symbol: z.string().trim().min(1).max(16).transform((value) => value.toUpperCase()),
  topic: z.string().trim().min(1).max(160),
  imageBase64: z.string().min(1).max(6 * 1024 * 1024),
});

const walletSchema = z.object({
  privateKey: z.string().regex(/^0x[a-fA-F0-9]{64}$/),
  expectedAddress: z.string().refine(isAddress, "Invalid connected wallet address").optional(),
});

const json = (response: ServerResponse, status: number, body: unknown, headers: Record<string, string> = {}) => {
  response.writeHead(status, {
    "content-type": "application/json; charset=utf-8",
    "cache-control": "no-store",
    "x-content-type-options": "nosniff",
    ...headers,
  });
  response.end(JSON.stringify(body));
};

const readJson = async (request: IncomingMessage, maxBytes = 65_536) => {
  const chunks: Buffer[] = [];
  let size = 0;
  for await (const chunk of request) {
    const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
    size += buffer.length;
    if (size > maxBytes) throw new Error("Request body exceeds upload limit");
    chunks.push(buffer);
  }
  return JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown;
};

const parseCookies = (header: string | undefined) => Object.fromEntries(
  (header ?? "").split(";").map((value) => value.trim()).filter(Boolean).map((entry) => {
    const index = entry.indexOf("=");
    return index < 0 ? [entry, ""] : [entry.slice(0, index), decodeURIComponent(entry.slice(index + 1))];
  }),
);

const constantTimeEqual = (left: string, right: string) => {
  const a = createHash("sha256").update(left).digest();
  const b = createHash("sha256").update(right).digest();
  return timingSafeEqual(a, b);
};

const requireInternalProxy = (request: IncomingMessage, config: AgentConfig) => {
  const expected = config.control.internalToken;
  const received = request.headers["x-control-internal-token"];
  return Boolean(expected && typeof received === "string" && constantTimeEqual(received, expected));
};

const getSession = (request: IncomingMessage) => {
  const id = parseCookies(request.headers.cookie).pons_operator_session;
  if (!id) return null;
  const session = sessions.get(id);
  if (!session || session.expiresAt <= Date.now()) {
    sessions.delete(id);
    return null;
  }
  return { id, ...session };
};

const validOrigin = (request: IncomingMessage, config: AgentConfig) => {
  const origin = request.headers.origin;
  return typeof origin === "string" && origin === config.control.publicOrigin;
};

const requireMutationAuth = (request: IncomingMessage, config: AgentConfig) => {
  const session = getSession(request);
  if (!session || !validOrigin(request, config)) return null;
  const csrf = request.headers["x-csrf-token"];
  return typeof csrf === "string" && constantTimeEqual(csrf, session.csrf) ? session : null;
};

const publicCommand = (row: ReturnType<AgentState["listOperatorCommands"]>[number]) => ({
  id: row.id,
  kind: row.kind,
  status: row.status,
  request: JSON.parse(row.requestJson) as Record<string, unknown>,
  result: row.resultJson ? JSON.parse(row.resultJson) as Record<string, unknown> : null,
  error: row.error,
  createdAt: row.createdAt,
  startedAt: row.startedAt,
  completedAt: row.completedAt,
});

const getStatus = async (config: AgentConfig) => {
  const executionConfig = resolveExecutionConfig(config);
  const state = new AgentState(config.stateDbPath);
  const active = state.getActiveWalletProfile();
  const walletAddress = active?.address ?? executionConfig.account?.address;
  const status = {
    mode: config.mode,
    wallet: active?.address ?? executionConfig.account?.address ?? null,
    walletSource: active ? "encrypted-vault" : executionConfig.account ? "environment" : null,
    walletConfiguredAt: active?.updatedAt ?? null,
    autoClaimEnabled: config.autoClaimEnabled,
    autoBuybackEnabled: config.autoBuybackEnabled,
    autoLaunchEnabled: config.autoLaunchEnabled,
    launches: walletAddress ? state.listLaunches(walletAddress) : [],
    executions: walletAddress ? state.listRecentExecutions(walletAddress, 20) : [],
    commands: walletAddress ? state.listOperatorCommands(walletAddress, 20).map(publicCommand) : [],
    cycles: state.listRecentCycles(5),
    wallets: state.listWalletProfiles(),
    balanceWei: null as string | null,
  };
  state.close();
  if (executionConfig.account) {
    const { publicClient } = createChainClients(executionConfig);
    status.balanceWei = (await publicClient.getBalance({ address: executionConfig.account.address })).toString();
  }
  return status;
};

export const startOperatorControl = (config: AgentConfig, onCommandQueued: () => void) => {
  if (!config.control.enabled) return null;
  const password = config.control.password;
  if (!password) throw new Error("Operator password is not configured");

  const server = createServer(async (request, response) => {
    try {
      if (!requireInternalProxy(request, config)) return json(response, 404, { error: "Not found" });
      const url = new URL(request.url ?? "/", "http://agent.internal");

      if (request.method === "POST" && url.pathname === "/api/operator/login") {
        if (!validOrigin(request, config)) return json(response, 403, { error: "Invalid request origin" });
        const client = request.socket.remoteAddress ?? "unknown";
        const existing = loginAttempts.get(client);
        if (existing && existing.resetAt > Date.now() && existing.count >= 8) {
          return json(response, 429, { error: "Too many login attempts" });
        }
        const body = z.object({ password: z.string().max(512) }).parse(await readJson(request));
        if (!constantTimeEqual(body.password, password)) {
          loginAttempts.set(client, {
            count: existing && existing.resetAt > Date.now() ? existing.count + 1 : 1,
            resetAt: Date.now() + 15 * 60_000,
          });
          return json(response, 401, { error: "Invalid password" });
        }
        loginAttempts.delete(client);
        const id = randomBytes(32).toString("base64url");
        const csrf = randomBytes(32).toString("base64url");
        sessions.set(id, { csrf, expiresAt: Date.now() + SESSION_TTL_MS });
        const secure = config.control.publicOrigin?.startsWith("https://") ? "; Secure" : "";
        return json(response, 200, { authenticated: true, csrf }, {
          "set-cookie": `pons_operator_session=${id}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${SESSION_TTL_MS / 1_000}${secure}`,
        });
      }

      const session = getSession(request);
      if (!session) return json(response, 401, { error: "Authentication required" });

      if (request.method === "GET" && url.pathname === "/api/operator/session") {
        return json(response, 200, { authenticated: true, csrf: session.csrf });
      }
      if (request.method === "GET" && url.pathname === "/api/operator/status") {
        return json(response, 200, await getStatus(config));
      }
      if (request.method === "POST" && url.pathname === "/api/operator/logout") {
        const authenticated = requireMutationAuth(request, config);
        if (!authenticated) return json(response, 403, { error: "Invalid CSRF token" });
        sessions.delete(authenticated.id);
        return json(response, 200, { authenticated: false }, {
          "set-cookie": "pons_operator_session=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0",
        });
      }
      if (request.method === "POST" && url.pathname === "/api/operator/wallet") {
        if (!requireMutationAuth(request, config)) return json(response, 403, { error: "Invalid CSRF token" });
        const body = walletSchema.parse(await readJson(request));
        const expectedAddress = body.expectedAddress ? getAddress(body.expectedAddress) : undefined;
        const address = provisionWallet(config, body.privateKey, expectedAddress);
        return json(response, 201, { address });
      }
      if (request.method === "POST" && url.pathname === "/api/operator/launch/predict") {
        if (!requireMutationAuth(request, config)) return json(response, 403, { error: "Invalid CSRF token" });
        const activeConfig = resolveExecutionConfig(config);
        if (!activeConfig.account) return json(response, 409, { error: "请先配置 Agent 执行钱包" });
        const draft = launchDraftSchema.parse(await readJson(request));
        const logo = draft.logo ?? await new TokenAssetPublisher(activeConfig).publish({
          name: draft.name,
          symbol: draft.symbol,
          topic: draft.topic,
        });
        const prediction = await predictLaunchToken(activeConfig, {
          name: draft.name,
          symbol: draft.symbol,
          logo,
          description: draft.description,
          socials: draft.socials,
        }, draft.initialBuyEth);
        return json(response, 200, {
          prediction,
          prepared: {
            ...draft,
            logo,
            salt: prediction.salt,
            expectedTokenAddress: prediction.predictedTokenAddress,
            expectedLaunchFeeWei: prediction.launchFeeWei,
          },
        });
      }
      if (request.method === "POST" && url.pathname === "/api/operator/logo") {
        if (!requireMutationAuth(request, config)) return json(response, 403, { error: "Invalid CSRF token" });
        const activeConfig = resolveExecutionConfig(config);
        if (!activeConfig.account) return json(response, 409, { error: "请先配置 Agent 执行钱包" });
        const body = logoUploadSchema.parse(await readJson(request, 8 * 1024 * 1024));
        const image = Buffer.from(body.imageBase64, "base64");
        if (image.toString("base64") !== body.imageBase64) throw new Error("头像文件不是有效的 PNG");
        const dimensions = validatePngImage(image);
        if (dimensions.width !== 1024 || dimensions.height !== 1024) throw new Error("头像必须是 1024x1024 PNG");
        const logo = await new TokenAssetPublisher(activeConfig).publishProvided({ name: body.name, symbol: body.symbol, topic: body.topic }, image);
        return json(response, 200, { logo });
      }
      if (request.method === "POST" && url.pathname === "/api/operator/commands/launch") {
        if (!requireMutationAuth(request, config)) return json(response, 403, { error: "Invalid CSRF token" });
        const activeConfig = resolveExecutionConfig(config);
        if (!activeConfig.account) return json(response, 409, { error: "Configure the Agent wallet first" });
        const command = launchCommandSchema.parse(await readJson(request));
        const state = new AgentState(config.stateDbPath);
        const id = randomUUID();
        state.enqueueOperatorCommand(activeConfig.account.address, { id, kind: "launch", request: command });
        state.close();
        onCommandQueued();
        return json(response, 202, { commandId: id, status: "queued" });
      }
      if (request.method === "POST" && url.pathname === "/api/operator/commands/claim") {
        if (!requireMutationAuth(request, config)) return json(response, 403, { error: "Invalid CSRF token" });
        const activeConfig = resolveExecutionConfig(config);
        if (!activeConfig.account) return json(response, 409, { error: "Configure the Agent wallet first" });
        const state = new AgentState(config.stateDbPath);
        const id = randomUUID();
        state.enqueueOperatorCommand(activeConfig.account.address, { id, kind: "claim", request: {} });
        state.close();
        onCommandQueued();
        return json(response, 202, { commandId: id, status: "queued" });
      }
      return json(response, 404, { error: "Not found" });
    } catch (error) {
      const message = error instanceof Error ? error.message : "Operator request failed";
      return json(response, error instanceof z.ZodError ? 422 : 500, { error: message });
    }
  });

  server.listen(config.control.port, "0.0.0.0");
  return server;
};
