import type { Repository } from '../../db/repository.js';

/** oidc-provider が要求する Adapter の構造的型（@types/oidc-provider に依存しないため最小定義）。 */
export interface OidcAdapter {
  upsert(id: string, payload: Record<string, unknown>, expiresIn: number): Promise<void>;
  find(id: string): Promise<Record<string, unknown> | undefined>;
  findByUserCode(userCode: string): Promise<Record<string, unknown> | undefined>;
  findByUid(uid: string): Promise<Record<string, unknown> | undefined>;
  consume(id: string): Promise<void>;
  destroy(id: string): Promise<void>;
  revokeByGrantId(grantId: string): Promise<void>;
}

const nowSec = (): number => Math.floor(Date.now() / 1000);

/**
 * `oidc-provider` の adapter factory。model 名ごとに 1 インスタンスが生成される。
 * `Client` だけは a2a_clients を読み、oidc client メタデータへ写像する（admin ゲート登録）。
 * それ以外（AccessToken/AuthorizationCode/Grant/Session/Interaction/...）は oidc_models に格納。
 */
export function makeOidcAdapterFactory(repo: Repository): (name: string) => OidcAdapter {
  return (name: string): OidcAdapter => {
    if (name === 'Client') return new ClientAdapter(repo);
    return new ModelAdapter(repo, name);
  };
}

class ModelAdapter implements OidcAdapter {
  constructor(private repo: Repository, private model: string) {}

  async upsert(id: string, payload: Record<string, unknown>, expiresIn: number): Promise<void> {
    const expiresAt = expiresIn ? nowSec() + expiresIn : null;
    const payloadWithExp: Record<string, unknown> = { ...payload };
    if (expiresAt !== null) {
      payloadWithExp.exp = expiresAt;
    }
    this.repo.oidcUpsert(this.model, id, payloadWithExp, {
      expiresAt,
      grantId: (payload.grantId as string) ?? null,
      userCode: (payload.userCode as string) ?? null,
      uid: (payload.uid as string) ?? null,
    });
  }

  async find(id: string): Promise<Record<string, unknown> | undefined> {
    const row = this.repo.oidcFind(this.model, id);
    if (!row) return undefined;
    if (row.consumedAt) row.payload.consumed = row.consumedAt;
    return this.notExpired(id, row.payload);
  }

  async findByUserCode(userCode: string): Promise<Record<string, unknown> | undefined> {
    const payload = this.repo.oidcFindByUserCode(userCode)?.payload;
    if (!payload) return undefined;
    const exp = payload.exp as number | undefined;
    if (typeof exp === 'number' && exp <= nowSec()) return undefined;
    return payload;
  }

  async findByUid(uid: string): Promise<Record<string, unknown> | undefined> {
    const payload = this.repo.oidcFindByUid(uid)?.payload;
    if (!payload) return undefined;
    const exp = payload.exp as number | undefined;
    if (typeof exp === 'number' && exp <= nowSec()) return undefined;
    return payload;
  }

  async consume(id: string): Promise<void> {
    this.repo.oidcConsume(this.model, id, nowSec());
  }

  async destroy(id: string): Promise<void> {
    this.repo.oidcDestroy(this.model, id);
  }

  async revokeByGrantId(grantId: string): Promise<void> {
    this.repo.oidcRevokeByGrantId(grantId);
  }

  /** payload.exp（oidc が書く）で失効判定。期限切れは undefined を返し、行も掃除する。 */
  private notExpired(id: string, payload: Record<string, unknown>): Record<string, unknown> | undefined {
    const exp = payload.exp as number | undefined;
    if (typeof exp === 'number' && exp <= nowSec()) {
      this.repo.oidcDestroy(this.model, id);
      return undefined;
    }
    return payload;
  }
}

/** a2a_clients → oidc client メタデータ。Adapter インターフェースの find 以外は no-op。 */
class ClientAdapter implements OidcAdapter {
  constructor(private repo: Repository) {}

  async find(id: string): Promise<Record<string, unknown> | undefined> {
    const c = this.repo.getA2aClient(id);
    if (!c || c.status !== 'active') return undefined;
    return {
      client_id: c.clientId,
      client_name: c.name,
      redirect_uris: c.redirectUris,
      token_endpoint_auth_method: c.tokenEndpointAuthMethod,
      grant_types: ['authorization_code'],
      response_types: ['code'],
      // Plan 1 registers PUBLIC clients only; no client_secret is provided.
      // Plan 2 confidential-client support will be added separately when needed.
    };
  }

  async upsert(): Promise<void> { /* admin API 経由でのみ登録。oidc 動的登録は使わない */ }
  async findByUserCode(): Promise<undefined> { return undefined; }
  async findByUid(): Promise<undefined> { return undefined; }
  async consume(): Promise<void> {}
  async destroy(): Promise<void> {}
  async revokeByGrantId(): Promise<void> {}
}
