import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { pbkdf2Sync, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
import { EncryptedAssessmentRecord } from '@/lib/assessment-types';

const STORE_PATH = path.join(process.cwd(), 'data', 'anonymous-store.json');
const HASH_ITERATIONS = 120_000;
const KEY_LENGTH = 32;

export interface AnonymousProfile {
  id: string;
  codeName: string;
  passwordHash: string;
  salt: string;
  createdAt: string;
  updatedAt: string;
  lastSeenAt: string;
}

export interface PublicAnonymousProfile {
  id: string;
  codeName: string;
  createdAt: string;
  updatedAt: string;
  lastSeenAt: string;
}

interface AnonymousStore {
  version: 2;
  profiles: AnonymousProfile[];
  records: EncryptedAssessmentRecord[];
}

export class AnonymousStoreError extends Error {
  constructor(
    message: string,
    public status = 400,
  ) {
    super(message);
  }
}

function normalizeCodeName(codeName: string) {
  return codeName.trim().replace(/\s+/g, ' ').slice(0, 40);
}

function assertCredentials(codeName: string, password: string) {
  const normalized = normalizeCodeName(codeName);
  if (normalized.length < 2) {
    throw new AnonymousStoreError('代号至少需要 2 个字符');
  }
  if (password.length < 4 || password.length > 128) {
    throw new AnonymousStoreError('恢复口令需要 4 到 128 个字符');
  }
  return normalized;
}

function hashPassword(password: string, salt: string) {
  return pbkdf2Sync(password, salt, HASH_ITERATIONS, KEY_LENGTH, 'sha256').toString('hex');
}

function verifyPassword(password: string, profile: AnonymousProfile) {
  const expected = Buffer.from(profile.passwordHash, 'hex');
  const actual = Buffer.from(hashPassword(password, profile.salt), 'hex');
  return expected.length === actual.length && timingSafeEqual(expected, actual);
}

function toPublicProfile(profile: AnonymousProfile): PublicAnonymousProfile {
  return {
    id: profile.id,
    codeName: profile.codeName,
    createdAt: profile.createdAt,
    updatedAt: profile.updatedAt,
    lastSeenAt: profile.lastSeenAt,
  };
}

async function loadStore(): Promise<AnonymousStore> {
  try {
    const raw = await readFile(STORE_PATH, 'utf8');
    const parsed = JSON.parse(raw) as Partial<AnonymousStore>;
    return {
      version: 2,
      profiles: Array.isArray(parsed.profiles) ? parsed.profiles : [],
      records: Array.isArray(parsed.records)
        ? parsed.records.filter((record) => record?.encrypted === true)
        : [],
    };
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
      return { version: 2, profiles: [], records: [] };
    }
    throw error;
  }
}

async function saveStore(store: AnonymousStore) {
  await mkdir(path.dirname(STORE_PATH), { recursive: true });
  const tempPath = `${STORE_PATH}.${process.pid}.${Date.now()}.tmp`;
  await writeFile(tempPath, JSON.stringify(store, null, 2), 'utf8');
  await rename(tempPath, STORE_PATH);
}

async function withStore<T>(callback: (store: AnonymousStore) => Promise<T> | T) {
  const store = await loadStore();
  const result = await callback(store);
  await saveStore(store);
  return result;
}

export async function loginAnonymousProfile(codeName: string, password: string) {
  const normalized = assertCredentials(codeName, password);
  return withStore((store) => {
    const now = new Date().toISOString();
    const existing = store.profiles.find(
      (profile) => profile.codeName.toLowerCase() === normalized.toLowerCase(),
    );

    if (existing) {
      if (!verifyPassword(password, existing)) {
        throw new AnonymousStoreError('代号或恢复口令不正确', 401);
      }
      existing.lastSeenAt = now;
      existing.updatedAt = now;
      return {
        profile: toPublicProfile(existing),
        records: store.records
          .filter((record) => record.profileId === existing.id)
          .sort((a, b) => b.completedAt.localeCompare(a.completedAt)),
      };
    }

    const salt = randomBytes(16).toString('hex');
    const profile: AnonymousProfile = {
      id: `anon_${randomUUID()}`,
      codeName: normalized,
      passwordHash: hashPassword(password, salt),
      salt,
      createdAt: now,
      updatedAt: now,
      lastSeenAt: now,
    };
    store.profiles.push(profile);
    return { profile: toPublicProfile(profile), records: [] };
  });
}

export async function addAnonymousRecord(
  codeName: string,
  password: string,
  encryptedRecord: EncryptedAssessmentRecord,
) {
  const { profile } = await loginAnonymousProfile(codeName, password);
  return withStore((store) => {
    const now = new Date().toISOString();
    const serverRecord: EncryptedAssessmentRecord = {
      ...encryptedRecord,
      id: encryptedRecord.id.startsWith('server_') ? encryptedRecord.id : `server_${encryptedRecord.id}`,
      profileId: profile.id,
    };
    const existingIndex = store.records.findIndex(
      (item) => item.id === serverRecord.id && item.profileId === profile.id,
    );
    if (existingIndex >= 0) {
      store.records[existingIndex] = serverRecord;
    } else {
      store.records.push(serverRecord);
    }
    const owner = store.profiles.find((item) => item.id === profile.id);
    if (owner) {
      owner.updatedAt = now;
      owner.lastSeenAt = now;
    }
    return serverRecord;
  });
}

export async function deleteAnonymousProfile(codeName: string, password: string) {
  const { profile } = await loginAnonymousProfile(codeName, password);
  return withStore((store) => {
    store.profiles = store.profiles.filter((item) => item.id !== profile.id);
    store.records = store.records.filter((item) => item.profileId !== profile.id);
    return { ok: true };
  });
}
