import { canonicalize, randomUuid, sha256Json, utf8 } from "../crypto";
import { loadRoleSigners } from "../keys";
import { createActionReceiptV04, type ActionReceiptV04 } from "../receipt";
import { parseStrictJsonRequestWithBytes, requireClosedObject } from "../strict-json";
import { HttpError, type JsonObject, type JsonValue } from "../types";
import { EFFECT_CLASS, EFFECT_LOCK_PROFILE, EFFECT_TARGET, RECEIVER_ID } from "./constants";
import { createFixtureGrant } from "./fixture";
import { EffectLockInputError, effectLockContext, verifyRedemption } from "./kernel";
import type {
  EffectLockEnv,
  EffectRecordSubject,
  RedemptionResponse,
} from "./types";

function wire(value: JsonValue, status = 200): Response {
  return new Response(`${canonicalize(value)}\n`, {
    status,
    headers: {
      "content-type": "application/json; charset=utf-8",
      "cache-control": "no-store",
      "x-content-type-options": "nosniff",
    },
  });
}

function parseStored<T>(value: string): T {
  return JSON.parse(value) as T;
}

interface GrantRow {
  grant_json: string;
  status: "UNSPENT" | "CONSUMED";
  redemption_digest: string | null;
  response_json: string | null;
}

interface EffectRow {
  observation_id: string;
  state_entry_json: string;
  record_json: string;
}

export class LockedSyntheticReceiver {
  constructor(
    private readonly state: DurableObjectState,
    private readonly env: EffectLockEnv,
  ) {
    state.blockConcurrencyWhile(async () => {
      state.storage.sql.exec(
        "CREATE TABLE IF NOT EXISTS grants(grant_attestation TEXT PRIMARY KEY NOT NULL,grant_id TEXT UNIQUE NOT NULL,receiver_nonce TEXT UNIQUE NOT NULL,authority_epoch TEXT NOT NULL,grant_json TEXT NOT NULL,status TEXT NOT NULL CHECK(status IN ('UNSPENT','CONSUMED')),redemption_digest TEXT,response_json TEXT)",
      );
      state.storage.sql.exec(
        "CREATE TABLE IF NOT EXISTS protected_effects(observation_id TEXT PRIMARY KEY NOT NULL,state_entry_json TEXT NOT NULL,record_json TEXT NOT NULL)",
      );
    });
  }

  private signers() {
    return loadRoleSigners(this.env);
  }

  private now(): string {
    if (this.env.EFFECT_LOCK_TEST_MODE === "true" && this.env.EFFECT_LOCK_FIXED_TIME !== undefined) {
      return this.env.EFFECT_LOCK_FIXED_TIME;
    }
    return new Date().toISOString();
  }

  private observationId(): string {
    if (
      this.env.EFFECT_LOCK_TEST_MODE === "true" &&
      this.env.EFFECT_LOCK_FIXED_OBSERVATION_ID !== undefined
    ) {
      return this.env.EFFECT_LOCK_FIXED_OBSERVATION_ID;
    }
    return randomUuid();
  }

  private async context() {
    const signers = this.signers();
    return effectLockContext({
      receiverIssuer: signers.target.issuer,
      authorizationIssuer: signers.authorization.issuer,
      boundaryIssuer: signers.boundary.issuer,
    });
  }

  private effectRows(): EffectRow[] {
    return this.state.storage.sql
      .exec<EffectRow>(
        "SELECT observation_id,state_entry_json,record_json FROM protected_effects ORDER BY rowid",
      )
      .toArray();
  }

  private async stateRoot(rows = this.effectRows()): Promise<string> {
    return sha256Json({
      profile: "bulla.effect-lock-protected-state/0.1-experimental",
      receiver_id: RECEIVER_ID,
      entries: rows.map((row) => parseStored<JsonObject>(row.state_entry_json)),
    });
  }

  private async issue(request: Request): Promise<Response> {
    const supplied = request.headers.get("x-receiver-authorization");
    if (
      this.env.EFFECT_LOCK_ISSUANCE_TOKEN === undefined ||
      supplied !== this.env.EFFECT_LOCK_ISSUANCE_TOKEN
    ) {
      throw new HttpError(403, "ISSUANCE_NOT_AUTHORIZED", "receiver authorization is required");
    }
    const parsed = await parseStrictJsonRequestWithBytes(request);
    requireClosedObject(parsed.value, [], []);
    if (new TextDecoder().decode(parsed.bytes) !== canonicalize(parsed.value)) {
      throw new HttpError(400, "NONCANONICAL_JSON", "grant request must use canonical JSON bytes");
    }
    const signers = this.signers();
    const context = await this.context();
    const grant = await createFixtureGrant({ signers, context });
    const grantSubject = grant.action.subject as JsonObject;
    const existing = this.state.storage.sql
      .exec<{ grant_attestation: string; grant_json: string; status: "UNSPENT" | "CONSUMED" }>(
        "SELECT grant_attestation,grant_json,status FROM grants WHERE grant_id=? OR receiver_nonce=?",
        grantSubject.grant_id,
        grantSubject.receiver_nonce,
      )
      .toArray()[0];
    const grantJson = canonicalize(grant as unknown as JsonValue);
    if (existing !== undefined && (existing.grant_attestation !== grant.hashes.attestation || existing.grant_json !== grantJson)) {
      throw new HttpError(409, "SEMANTIC_GRANT_CONFLICT", "grant identity or nonce already has receiver standing");
    }
    if (existing === undefined) {
      this.state.storage.sql.exec(
        "INSERT INTO grants(grant_attestation,grant_id,receiver_nonce,authority_epoch,grant_json,status,redemption_digest,response_json) VALUES(?,?,?,?,?,'UNSPENT',NULL,NULL)",
        grant.hashes.attestation,
        grantSubject.grant_id,
        grantSubject.receiver_nonce,
        grantSubject.authority_epoch,
        grantJson,
      );
    }
    const stored = this.state.storage.sql
      .exec<{ status: "UNSPENT" | "CONSUMED" }>(
        "SELECT status FROM grants WHERE grant_attestation=?",
        grant.hashes.attestation,
      )
      .toArray()[0];
    if (stored?.status === "CONSUMED") {
      throw new HttpError(409, "GRANT_ALREADY_CONSUMED", "the fixed grant cannot be reissued");
    }
    return wire({ grant: grant as unknown as JsonValue, status: "UNSPENT" }, 201);
  }

  private stateView(): Promise<Response> {
    return (async () => {
      const grants = this.state.storage.sql
        .exec<{ grant_attestation: string; status: string; redemption_digest: string | null }>(
          "SELECT grant_attestation,status,redemption_digest FROM grants ORDER BY rowid",
        )
        .toArray();
      const effects = this.effectRows();
      return wire({
        profile: EFFECT_LOCK_PROFILE,
        receiver_id: RECEIVER_ID,
        state_root: await this.stateRoot(effects),
        grants,
        protected_effects: effects.map((row) => ({
          observation_id: row.observation_id,
          effect_record: parseStored<JsonValue>(row.record_json),
        })),
      });
    })();
  }

  private effectView(): Response {
    return wire({
      profile: EFFECT_LOCK_PROFILE,
      effects: this.effectRows().map((row) => parseStored<JsonValue>(row.record_json)),
    });
  }

  private async redeem(request: Request): Promise<Response> {
    const parsed = await parseStrictJsonRequestWithBytes(request);
    const value = requireClosedObject(
      parsed.value,
      [
        "profile", "grant", "mandate", "boundary_decision", "authzen_request",
        "authzen_response", "effect_arguments", "receiver_nonce", "evidence_bundle_root",
      ],
      [
        "profile", "grant", "mandate", "boundary_decision", "authzen_request",
        "authzen_response", "effect_arguments", "receiver_nonce", "evidence_bundle_root",
      ],
    );
    if (new TextDecoder().decode(parsed.bytes) !== canonicalize(value)) {
      throw new HttpError(400, "NONCANONICAL_JSON", "redemption must use canonical JSON bytes");
    }
    const grantValue = value.grant;
    if (grantValue === null || typeof grantValue !== "object" || Array.isArray(grantValue)) {
      throw new HttpError(400, "INVALID_REDEMPTION", "grant must be an object");
    }
    const grant = grantValue as unknown as ActionReceiptV04;
    const attestation = grant.hashes?.attestation;
    if (typeof attestation !== "string") {
      throw new HttpError(400, "INVALID_REDEMPTION", "grant attestation is required");
    }
    const row = this.state.storage.sql
      .exec<GrantRow>(
        "SELECT grant_json,status,redemption_digest,response_json FROM grants WHERE grant_attestation=?",
        attestation,
      )
      .toArray()[0];
    if (row === undefined) {
      throw new HttpError(403, "GRANT_NOT_ISSUED", "grant is not present in receiver state");
    }
    const inputDigest = await sha256Json(value);
    if (row.status === "CONSUMED") {
      if (row.redemption_digest !== inputDigest || row.response_json === null) {
        throw new HttpError(409, "GRANT_ALREADY_CONSUMED", "grant was consumed by another redemption");
      }
      return new Response(row.response_json, {
        status: 200,
        headers: {
          "content-type": "application/json; charset=utf-8",
          "cache-control": "no-store",
          "x-content-type-options": "nosniff",
        },
      });
    }

    const priorRoot = await this.stateRoot();
    const verified = await verifyRedemption({
      value,
      persistedGrant: parseStored<ActionReceiptV04>(row.grant_json),
      acceptedContext: await this.context(),
      now: this.now(),
      priorStateRoot: priorRoot,
    });
    if (request.headers.get("x-effect-lock-test-crash") === "before_commit") {
      if (this.env.EFFECT_LOCK_TEST_MODE !== "true") {
        throw new HttpError(400, "UNKNOWN_TEST_CONTROL", "test controls are disabled");
      }
      throw new Error("synthetic crash before commit");
    }

    const signers = this.signers();
    const observationId = this.observationId();
    const stateEntry: JsonObject = {
      observation_id: observationId,
      run_id: (grant.action.subject as JsonObject).run_id as string,
      effect_class: EFFECT_CLASS,
      arguments_hash: verified.effect_arguments_hash,
    };
    const newRoot = await sha256Json({
      profile: "bulla.effect-lock-protected-state/0.1-experimental",
      receiver_id: RECEIVER_ID,
      entries: [
        ...this.effectRows().map((effect) => parseStored<JsonObject>(effect.state_entry_json)),
        stateEntry,
      ],
    });
    const context = await this.context();
    const effectSubject: EffectRecordSubject = {
      profile: EFFECT_LOCK_PROFILE,
      grant_attestation: verified.grant_attestation,
      redemption_digest: verified.redemption_digest,
      effect_class: EFFECT_CLASS,
      arguments_hash: verified.effect_arguments_hash,
      run_id: stateEntry.run_id as string,
      receiver_id: RECEIVER_ID,
      target: EFFECT_TARGET,
      evidence_policy_hash: context.evidence_policy_hash,
      verifier_hash: context.verifier_hash,
      runtime_graph_root: context.runtime_graph_root,
      role_set_hash: context.role_set_hash,
      semantic_epoch: context.semantic_epoch,
      authority_epoch: context.authority_epoch,
      previous_state_root: priorRoot,
      new_state_root: newRoot,
      observation_id: observationId,
      issuer_role: "target",
    };
    const effectRecord = await createActionReceiptV04({
      actionType: "effect.execute",
      subject: effectSubject,
      eventId: observationId,
      claimedAt: this.now(),
      evidence: [
        {
          name: "redemption",
          hash: verified.redemption_digest,
          grounding: "self_asserted",
        },
      ],
      signer: signers.target,
      issuerRole: "target",
    });
    const response: RedemptionResponse = {
      profile: "bulla.effect-redemption/0.1-experimental",
      status: "COMMITTED",
      grant_status: "CONSUMED",
      replay: false,
      observation_id: observationId,
      prior_state_root: priorRoot,
      new_state_root: newRoot,
      redemption_digest: verified.redemption_digest,
      effect_record: effectRecord as unknown as JsonObject,
    };
    const responseJson = `${canonicalize(response)}\n`;

    await this.state.storage.transaction(async () => {
      const current = this.state.storage.sql
        .exec<GrantRow>(
          "SELECT grant_json,status,redemption_digest,response_json FROM grants WHERE grant_attestation=?",
          attestation,
        )
        .toArray()[0];
      if (current === undefined || current.status !== "UNSPENT") {
        throw new HttpError(409, "GRANT_ALREADY_CONSUMED", "grant is no longer unspent");
      }
      this.state.storage.sql.exec(
        "UPDATE grants SET status='CONSUMED',redemption_digest=?,response_json=? WHERE grant_attestation=? AND status='UNSPENT'",
        verified.redemption_digest,
        responseJson,
        attestation,
      );
      this.state.storage.sql.exec(
        "INSERT INTO protected_effects(observation_id,state_entry_json,record_json) VALUES(?,?,?)",
        observationId,
        canonicalize(stateEntry),
        canonicalize(effectRecord as unknown as JsonValue),
      );
    });

    if (request.headers.get("x-effect-lock-test-crash") === "after_commit") {
      if (this.env.EFFECT_LOCK_TEST_MODE !== "true") {
        throw new HttpError(400, "UNKNOWN_TEST_CONTROL", "test controls are disabled");
      }
      throw new Error("synthetic crash after commit");
    }
    return new Response(responseJson, {
      status: 200,
      headers: {
        "content-type": "application/json; charset=utf-8",
        "cache-control": "no-store",
        "x-content-type-options": "nosniff",
      },
    });
  }

  async fetch(request: Request): Promise<Response> {
    try {
      const url = new URL(request.url);
      if (request.method === "POST" && url.pathname === "/grant") return await this.issue(request);
      if (request.method === "POST" && url.pathname === "/redeem") return await this.redeem(request);
      if (request.method === "POST" && url.pathname === "/effect") {
        throw new HttpError(403, "REDEMPTION_REQUIRED", "protected effect requires complete redemption");
      }
      if (request.method === "GET" && url.pathname === "/state") return await this.stateView();
      if (request.method === "GET" && url.pathname === "/effect") return this.effectView();
      throw new HttpError(404, "NOT_FOUND", "locked receiver route not found");
    } catch (error) {
      if (error instanceof HttpError) return wire({ error: { code: error.code, message: error.message } }, error.status);
      if (error instanceof EffectLockInputError) {
        const status = ["INVALID_REDEMPTION", "OPEN_SHAPE"].includes(error.code) ? 400 : 403;
        return wire({ error: { code: error.code, message: error.message } }, status);
      }
      return wire({ error: { code: "VERIFIER_FAILURE", message: "locked receiver failed closed" } }, 500);
    }
  }
}

const API = "/experimental/effect-lock";

const worker: ExportedHandler<EffectLockEnv> = {
  async fetch(request, env): Promise<Response> {
    const url = new URL(request.url);
    const receiver = env.LOCKED_SYNTHETIC_RECEIVER.get(
      env.LOCKED_SYNTHETIC_RECEIVER.idFromName(RECEIVER_ID),
    );
    let internalPath: string | null = null;
    if (url.pathname === `${API}/grants` && request.method === "POST") internalPath = "/grant";
    if (url.pathname === `${API}/redemptions` && request.method === "POST") internalPath = "/redeem";
    if (url.pathname === `${API}/state` && request.method === "GET") internalPath = "/state";
    if (url.pathname === `${API}/effect` && request.method === "GET") internalPath = "/effect";
    if (url.pathname === `${API}/effect` && request.method === "POST") internalPath = "/effect";
    if (internalPath === null) return wire({ error: { code: "NOT_FOUND", message: "route not found" } }, 404);
    const headers = new Headers(request.headers);
    headers.delete("x-effect-lock-test-crash");
    const init: RequestInit = {
      method: request.method,
      headers,
    };
    if (request.method !== "GET") init.body = request.body;
    return receiver.fetch(`https://internal${internalPath}`, init);
  },
};

export default worker;
