import { randomUUID } from 'node:crypto';

import { tryGetHttpRequestContext } from '@adapters/http/RequestContext';
import type { AuthenticatedUser } from '@core/identity/application/ports/IdentityProviderPort';
import type { AuthSession, IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
import type { Logger } from '@core/shared/domain/Logger';

const COOKIE_NAME = 'gp_session';
const SESSION_TTL_MS = 3600 * 1000; // 1 hour
const REMEMBER_ME_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days

type StoredSession = AuthSession;

function readCookieHeader(headerValue: string | undefined): Record<string, string> {
  if (!headerValue) return {};
  const pairs = headerValue.split(';');
  const record: Record<string, string> = {};
  for (const pair of pairs) {
    const trimmed = pair.trim();
    if (!trimmed) continue;
    const eq = trimmed.indexOf('=');
    if (eq === -1) continue;
    const name = trimmed.slice(0, eq).trim();
    const value = trimmed.slice(eq + 1).trim();
    if (!name) continue;
    record[name] = decodeURIComponent(value);
  }
  return record;
}

function buildSetCookieHeader(options: {
  name: string;
  value: string;
  maxAgeSeconds: number;
  httpOnly: boolean;
  sameSite: 'Lax' | 'Strict' | 'None';
  secure: boolean;
  path?: string;
}): string {
  const parts: string[] = [];
  parts.push(`${options.name}=${encodeURIComponent(options.value)}`);
  parts.push(`Max-Age=${options.maxAgeSeconds}`);
  parts.push(`Path=${options.path ?? '/'}`);
  if (options.httpOnly) parts.push('HttpOnly');
  parts.push(`SameSite=${options.sameSite}`);
  if (options.secure) parts.push('Secure');
  return parts.join('; ');
}

function appendSetCookieHeader(existing: string | string[] | undefined, next: string): string[] {
  if (!existing) return [next];
  if (Array.isArray(existing)) return [...existing, next];
  return [existing, next];
}

export class CookieIdentitySessionAdapter implements IdentitySessionPort {
  private readonly sessionsByToken = new Map<string, StoredSession>();

  constructor(private readonly logger: Logger) {
    this.logger.info('[CookieIdentitySessionAdapter] initialized (in-memory cookie sessions).');
  }

  async getCurrentSession(): Promise<AuthSession | null> {
    const ctx = tryGetHttpRequestContext();
    if (!ctx) {
      // Called outside HTTP request (e.g. some unit tests). Behave as no session.
      return null;
    }

    const cookies = readCookieHeader(ctx.req.headers.cookie);
    const token = cookies[COOKIE_NAME];
    if (!token) return null;

    const session = this.sessionsByToken.get(token) ?? null;
    if (!session) return null;

    const now = Date.now();
    if (session.expiresAt <= now) {
      this.sessionsByToken.delete(token);
      return null;
    }

    return session;
  }

  async createSession(user: AuthenticatedUser, options?: { rememberMe?: boolean }): Promise<AuthSession> {
    const issuedAt = Date.now();
    const ttlMs = options?.rememberMe ? REMEMBER_ME_TTL_MS : SESSION_TTL_MS;
    const expiresAt = issuedAt + ttlMs;

    const token = `gp_${randomUUID()}`;

    const session: AuthSession = {
      user,
      issuedAt,
      expiresAt,
      token,
    };

    this.sessionsByToken.set(token, session);

    const ctx = tryGetHttpRequestContext();
    if (ctx) {
      const setCookie = buildSetCookieHeader({
        name: COOKIE_NAME,
        value: token,
        maxAgeSeconds: Math.floor(ttlMs / 1000),
        httpOnly: true,
        sameSite: 'Lax',
        secure: false,
      });

      const existing = ctx.res.getHeader('Set-Cookie');
      ctx.res.setHeader('Set-Cookie', appendSetCookieHeader(existing as any, setCookie));
    }

    return session;
  }

  async clearSession(): Promise<void> {
    const ctx = tryGetHttpRequestContext();

    if (ctx) {
      const cookies = readCookieHeader(ctx.req.headers.cookie);
      const token = cookies[COOKIE_NAME];
      if (token) {
        this.sessionsByToken.delete(token);
      }

      const setCookie = buildSetCookieHeader({
        name: COOKIE_NAME,
        value: '',
        maxAgeSeconds: 0,
        httpOnly: true,
        sameSite: 'Lax',
        secure: false,
      });

      const existing = ctx.res.getHeader('Set-Cookie');
      ctx.res.setHeader('Set-Cookie', appendSetCookieHeader(existing as any, setCookie));
      return;
    }

    // No request context: nothing to clear from cookie; just clear all in-memory sessions.
    this.sessionsByToken.clear();
  }
}