import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
import { pool } from './db';
import { config } from './config';
import { cleanupExpiredGuardianContactData } from './guardians';
import { encryptSensitive } from './privacy';
import {
  call,
  completeProfile,
  contactFingerprints,
  createdUserIds,
  fingerprintKeyFor,
  register,
  type Session,
} from './smokeSupport';

export interface SmokeContext {
  first: Session;
  second: Session;
  unrelated: Session;
  representative: Session;
  admin: Session;
  accepted: { journey: { id: string }; requestId: string };
}

export const runProfileSmoke = async (): Promise<SmokeContext> => {
  const first = await register('first');
  const second = await register('second');
  const unrelated = await register('unrelated');
  const cleanupCandidate = await register('cleanup');
  const rejectedCandidate = await register('rejected');
  const admin = await call<Session>('/auth/login', 200, undefined, 'POST', {
    email: config.seedAdminEmail,
    password: config.seedAdminPassword,
  });
  const deletionSuffix = randomUUID();
  const deletionEmail = `smoke-delete-${deletionSuffix}@example.test`;
  const deletionPassword = `Smoke-${deletionSuffix}-Password!`;
  const deletionAccount = await call<Session>('/v1/auth/register', 201, undefined, 'POST', {
    email: deletionEmail, password: deletionPassword, displayName: 'Smoke deletion',
  });
  createdUserIds.push(deletionAccount.user.id);
  await call('/v1/me/account', 401, deletionAccount.token, 'DELETE', { password: 'Wrong-Password!' });
  const deletion = await call<{ deleted: boolean; purgeAfter: string }>(
    '/v1/me/account', 200, deletionAccount.token, 'DELETE', { password: deletionPassword },
  );
  assert.equal(deletion.deleted, true);
  assert(Date.parse(deletion.purgeAfter) > Date.now() + 29 * 86_400_000);
  await call('/auth/login', 401, undefined, 'POST', { email: deletionEmail, password: deletionPassword });
  const retained = await call<{ users: Array<{ id: string; deletedAt: string | null; purgeAfter: string | null }> }>(
    '/admin/overview', 200, admin.token,
  );
  const retainedAccount = retained.users.find((item) => item.id === deletionAccount.user.id);
  assert(retainedAccount?.deletedAt && retainedAccount.purgeAfter);

  const linkedGuardianTarget = await register('guardian-link-target');
  const linkedGuardianSource = await register('guardian-link-source');
  const guardianLinkQr = await call<{ payload: string }>(
    '/v1/me/guardian-qr', 201, linkedGuardianTarget.token, 'POST',
  );
  const guardianLinkClaim = await call<{ enrollmentToken: string }>(
    '/v1/guardian-qr/claim', 200, undefined, 'POST', { payload: guardianLinkQr.payload },
  );
  await call('/v1/guardian-qr/link-existing', 400, linkedGuardianSource.token, 'POST', {
    enrollmentToken: guardianLinkClaim.enrollmentToken,
    phone: '+4915112345689',
    confirmed: false,
  });
  const linkedGuardian = await call<Session>(
    '/v1/guardian-qr/link-existing', 201, linkedGuardianSource.token, 'POST', {
      enrollmentToken: guardianLinkClaim.enrollmentToken,
      phone: '+4915112345689',
      confirmed: true,
    },
  );
  await call('/auth/me', 200, linkedGuardianSource.token);
  const linkedState = await call<{ user: { accountType: string } }>(
    '/auth/me', 200, linkedGuardian.token,
  );
  assert.equal(linkedState.user.accountType, 'candidate');
  const linkedGuardianState = await call<{ guardian: { guardianOf: Array<{ candidateName: string }> } }>(
    '/v1/me/guardian-verification', 200, linkedGuardian.token,
  );
  assert(linkedGuardianState.guardian.guardianOf.some((item) =>
    item.candidateName === 'Smoke guardian-link-target'));

  await call('/v1/me/no-guardian', 400, unrelated.token, 'POST', {
    representativePhone: '00491234567890', representativeFullName: 'Smoke representative',
  });
  const representativeRequest = await call<{ shareUrl: string }>('/v1/me/no-guardian', 201, unrelated.token, 'POST', {
    representativePhone: '+491234567890', representativeFullName: 'Smoke representative',
  });
  const representativeClaim = await call<{ enrollmentToken: string }>(
    '/v1/representative-invitations/claim', 200, undefined, 'POST', { payload: representativeRequest.shareUrl },
  );
  await call('/v1/representative-invitations/enroll', 409, undefined, 'POST', {
    enrollmentToken: representativeClaim.enrollmentToken, fullName: 'Completely different person',
    phone: '+491234567890', password: 'Smoke-Representative-Password!',
    passwordConfirmation: 'Smoke-Representative-Password!',
  });
  const representative = await call<Session>('/v1/representative-invitations/enroll', 201, undefined, 'POST', {
    enrollmentToken: representativeClaim.enrollmentToken, fullName: 'Smoke representativf',
    phone: '01234567890', password: 'Smoke-Representative-Password!',
    passwordConfirmation: 'Smoke-Representative-Password!',
  });
  createdUserIds.push(representative.user.id);
  const representativeLogin = await call<Session>('/auth/login', 200, undefined, 'POST', {
    identifier: representative.user.username, password: 'Smoke-Representative-Password!',
  });
  assert.equal(representativeLogin.user.id, representative.user.id);
  const invitations = await call<{ invitations: Array<{ id: string; status: string; candidateName: string }> }>(
    '/v1/me/representative-invitations', 200, representative.token,
  );
  const repInvitation = invitations.invitations.find((item) => item.candidateName === 'Smoke unrelated');
  assert(repInvitation);
  assert.equal(repInvitation.status, 'accepted');
  const guardianExceptions = await call<{ requests: Array<{ id: string; candidateUserId: string; phone: string }> }>(
    '/v1/admin/guardian-exceptions', 200, admin.token,
  );
  const manualReview = guardianExceptions.requests.find((item) => item.candidateUserId === unrelated.user.id);
  assert(manualReview);
  assert.equal(manualReview.phone, '+491234567890');
  await call(`/v1/admin/guardian-exceptions/${manualReview.id}`, 400, admin.token, 'PATCH', {
    decision: 'approved', note: 'A note alone must not approve an unconfirmed telephone call.',
  });
  await call(`/v1/admin/guardian-exceptions/${manualReview.id}`, 200, admin.token, 'PATCH', {
    decision: 'approved', note: 'Synthetic telephone verification completed by the administrator.',
    contactConfirmed: true,
  });
  const manuallyVerified = await call<{ guardian: { satisfied: boolean; exception: {
    status: string; representativeUserId: string; representativeName: string;
    invitationStatus: string;
  } } }>(
    '/v1/me/guardian-verification', 200, unrelated.token,
  );
  assert.equal(manuallyVerified.guardian.satisfied, true);
  assert.equal(manuallyVerified.guardian.exception.status, 'approved');
  assert.equal(manuallyVerified.guardian.exception.representativeName, 'Smoke representativf');
  assert.equal(representative.user.id, manuallyVerified.guardian.exception.representativeUserId);
  await call(`/v1/me/representative-invitations/${repInvitation.id}/respond`, 409,
    representative.token, 'POST', { decision: 'accepted' });
  const acceptedRepresentative = await call<{ guardian: { satisfied: boolean } }>(
    '/v1/me/guardian-verification', 200, unrelated.token,
  );
  assert.equal(acceptedRepresentative.guardian.satisfied, true);
  const pendingRepresentative = await call<{ shareUrl: string }>('/v1/me/no-guardian', 201, rejectedCandidate.token, 'POST', {
    representativePhone: '+491234567891', representativeFullName: 'Smoke first',
  });
  const rejectedRequests = await call<{ requests: Array<{ id: string; candidateUserId: string }> }>(
    '/v1/admin/guardian-exceptions', 200, admin.token,
  );
  const rejectedReview = rejectedRequests.requests.find(
    (item) => item.candidateUserId === rejectedCandidate.user.id,
  );
  assert(rejectedReview);
  await call(`/v1/admin/guardian-exceptions/${rejectedReview.id}`, 409, admin.token, 'PATCH', {
    decision: 'approved', note: 'An unregistered representative cannot be approved.', contactConfirmed: true,
  });
  await call(`/v1/admin/guardian-exceptions/${rejectedReview.id}`, 200, admin.token, 'PATCH', {
    decision: 'rejected', note: 'The representative did not complete the invitation.', contactConfirmed: false,
  });
  await call('/v1/representative-invitations/claim', 410, undefined, 'POST', {
    payload: pendingRepresentative.shareUrl,
  });
  const renewableRequest = await call<{ shareUrl: string }>('/v1/me/no-guardian', 201, rejectedCandidate.token, 'POST', {
    representativePhone: '+491234567891', representativeFullName: 'Smoke representative',
  });
  const renewableState = await call<{ guardian: { exception: {
    id: string; invitationStatus: string; shareUrl: string | null;
  } } }>('/v1/me/guardian-verification', 200, rejectedCandidate.token);
  assert.equal(renewableState.guardian.exception.shareUrl, renewableRequest.shareUrl);
  await pool.query(
    "UPDATE guardian_representative_invitations SET expires_at=NOW()-INTERVAL '1 second' WHERE exception_id=$1",
    [renewableState.guardian.exception.id],
  );
  await cleanupExpiredGuardianContactData();
  const expiredRepresentativeState = await call<{ guardian: { exception: {
    invitationStatus: string; shareUrl: string | null;
  } } }>('/v1/me/guardian-verification', 200, rejectedCandidate.token);
  assert.equal(expiredRepresentativeState.guardian.exception.invitationStatus, 'revoked');
  assert.equal(expiredRepresentativeState.guardian.exception.shareUrl, null);
  const reissued = await call<{ shareUrl: string }>(
    `/v1/me/no-guardian/${renewableState.guardian.exception.id}/reissue`, 200,
    rejectedCandidate.token, 'POST',
  );
  await call('/v1/representative-invitations/claim', 200, undefined, 'POST', { payload: reissued.shareUrl });
  await call(`/v1/me/no-guardian/${renewableState.guardian.exception.id}/cancel`, 200,
    rejectedCandidate.token, 'POST');
  await call('/v1/me/guardian-qr', 201, rejectedCandidate.token, 'POST');

  const legacyOwner = await register('legacy-owner');
  const legacyRepresentative = await register('legacy-representative');
  const legacyExceptionId = randomUUID();
  await pool.query(
    `INSERT INTO guardian_exceptions
       (id,candidate_user_id,phone_encrypted,phone_last_four,representative_name,representative_user_id)
     VALUES ($1,$2,$3,$4,$5,$6)`,
    [legacyExceptionId, legacyOwner.user.id, encryptSensitive('+4915123456790'), '6790',
      'Smoke legacy representative', legacyRepresentative.user.id],
  );
  await pool.query(
    `INSERT INTO guardian_representative_invitations
       (id,exception_id,candidate_user_id,representative_user_id,status,invited_full_name,expires_at)
     VALUES ($1,$2,$3,$4,'revoked',$5,NOW()-INTERVAL '1 minute')`,
    [randomUUID(), legacyExceptionId, legacyOwner.user.id, legacyRepresentative.user.id,
      'Smoke legacy representative'],
  );
  const legacyQueue = await call<{ requests: Array<{
    id: string; canApproveExistingAccount: boolean;
  }> }>('/v1/admin/guardian-exceptions', 200, admin.token);
  assert.equal(legacyQueue.requests.find((item) => item.id === legacyExceptionId)?.canApproveExistingAccount, true);
  await call(`/v1/admin/guardian-exceptions/${legacyExceptionId}`, 200, admin.token, 'PATCH', {
    decision: 'approved', note: 'Existing empty account verified through a confirmed administrator call.',
    contactConfirmed: true,
  });
  await call('/auth/me', 401, legacyRepresentative.token);
  const converted = await pool.query<{ account_type: string; profile_count: number }>(
    `SELECT u.account_type,COUNT(p.user_id)::int AS profile_count FROM users u
     LEFT JOIN profiles p ON p.user_id=u.id WHERE u.id=$1 GROUP BY u.account_type`,
    [legacyRepresentative.user.id],
  );
  assert.equal(converted.rows[0].account_type, 'representative');
  assert.equal(converted.rows[0].profile_count, 0);

  const expiringQr = await call<{ payload: string }>(
    '/v1/me/guardian-qr', 201, cleanupCandidate.token, 'POST',
  );
  await pool.query(
    "UPDATE guardian_qr_challenges SET expires_at=NOW()-INTERVAL '1 second' WHERE candidate_user_id=$1",
    [cleanupCandidate.user.id],
  );
  await call('/v1/guardian-qr/claim', 410, undefined, 'POST', { payload: expiringQr.payload });
  const claimedQr = await call<{ payload: string }>(
    '/v1/me/guardian-qr', 201, cleanupCandidate.token, 'POST',
  );
  const claimForCleanup = await call<{ enrollmentToken: string }>(
    '/v1/guardian-qr/claim', 200, undefined, 'POST', { payload: claimedQr.payload },
  );
  const cleanupGuardian = await call<Session>(
    '/v1/guardian-qr/enroll', 201, undefined, 'POST', {
      enrollmentToken: claimForCleanup.enrollmentToken,
      fullName: 'Cleanup Guardian', phone: '+4915112345679',
      password: 'Cleanup-Guardian-Password!', passwordConfirmation: 'Cleanup-Guardian-Password!',
    },
  );
  createdUserIds.push(cleanupGuardian.user.id);
  const cleanupState = await call<{ guardian: { invitations: Array<{ id: string }> } }>(
    '/v1/me/guardian-verification', 200, cleanupCandidate.token,
  );
  const expiringInvitation = { invitationId: cleanupState.guardian.invitations[0].id };
  const expiringKey = await fingerprintKeyFor(cleanupCandidate, expiringInvitation.invitationId);
  await call(`/v1/guardian-invitations/${expiringInvitation.invitationId}/contacts`, 200,
    cleanupCandidate.token, 'PUT', { fingerprintVersion: 2, contacts: contactFingerprints(expiringKey) });
  await pool.query("UPDATE guardian_invitations SET expires_at=NOW()-INTERVAL '1 minute' WHERE id=$1",
    [expiringInvitation.invitationId]);
  await call(`/v1/guardian-invitations/${expiringInvitation.invitationId}/contacts`, 409,
    cleanupCandidate.token, 'PUT', { fingerprintVersion: 2, contacts: contactFingerprints(expiringKey) });
  await cleanupExpiredGuardianContactData();
  const expired = await pool.query<{ status: string; hash_count: number }>(
    `SELECT i.status, COUNT(h.contact_digest)::int AS hash_count
     FROM guardian_invitations i LEFT JOIN guardian_contact_hashes h ON h.invitation_id=i.id
     WHERE i.id=$1 GROUP BY i.status`, [expiringInvitation.invitationId],
  );
  assert.equal(expired.rows[0].status, 'revoked');
  assert.equal(expired.rows[0].hash_count, 0);

  const drafts = await call<{ profiles: Array<{ userId: string }> }>('/v1/discover', 200, first.token);
  assert(!drafts.profiles.some((profile) => profile.userId === second.user.id));

  for (const [session, name] of [[first, 'First'], [second, 'Second']] as const) {
    const saved = await call<{ profile: { version: number; isPublished: boolean; isComplete: boolean;
      approvalStatus: string; childrenCount: number; preferredReligion: string } }>(
      '/v1/me/profile', 200, session.token, 'PATCH', {
        ...completeProfile(name),
        preferredReligion: 'non_muslim',
        expectedVersion: 1,
      },
    );
    assert.equal(saved.profile.version, 2);
    assert.equal(saved.profile.isPublished, true);
    assert.equal(saved.profile.isComplete, true);
    assert.equal(saved.profile.approvalStatus, 'approved');
    assert.equal(saved.profile.childrenCount, 0);
    assert.equal(saved.profile.preferredReligion, 'muslim');
  }

  await call('/v1/admin/profiles', 403, first.token);
  const queue = await call<{ profiles: Array<{ userId: string }> }>('/v1/admin/profiles', 200, admin.token);
  assert(queue.profiles.some((profile) => profile.userId === first.user.id));
  assert(queue.profiles.some((profile) => profile.userId === second.user.id));
  await call(`/v1/admin/profiles/${first.user.id}/review`, 409, admin.token, 'PATCH',
    { decision: 'approved', note: '' });

  for (const immutableChange of [
    { displayName: 'Changed name' }, { gender: 'woman' }, { birthYear: 1992 },
    { birthPlace: 'Hamburg' }, { skinTone: 'fair' }, { eyeColor: 'green' },
    { heightCm: 180 }, { appearanceNote: 'Changed appearance' },
    { bio: 'A changed synthetic biography that must remain locked after first completion.' },
    { valuesText: 'Changed values that must remain locked after first completion.' },
  ]) {
    await call('/v1/me/profile', 409, first.token, 'PATCH', {
      ...completeProfile('First'), ...immutableChange, expectedVersion: 2,
    });
  }
  const unchangedFirst = await call<{ profile: { isPublished: boolean; version: number } }>(
    '/v1/me/profile', 200, first.token,
  );
  assert.equal(unchangedFirst.profile.isPublished, true);
  assert.equal(unchangedFirst.profile.version, 2);

  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), childrenCount: 6, expectedVersion: 1,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), gender: 'other', expectedVersion: 1,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), preferredGender: 'any', expectedVersion: 1,
  });
  await call('/v1/me/profile', 200, unrelated.token, 'PATCH', {
    ...completeProfile('First'), maritalStatus: 'married', childrenCount: 5, expectedVersion: 1,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), gender: 'woman', maritalStatus: 'married', expectedVersion: 2,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), preferredMaritalStatus: 'married', expectedVersion: 2,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), preferredHairColors: ['bald'], expectedVersion: 2,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), musicPreferences: [], expectedVersion: 2,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), musicPreferences: ['none', 'rap'], expectedVersion: 2,
  });
  await call('/v1/me/profile', 400, unrelated.token, 'PATCH', {
    ...completeProfile('First'), maritalStatus: 'other', expectedVersion: 2,
  });
  const mutableProfile = await call<{ profile: { version: number; weightKg: number } }>(
    '/v1/me/profile', 200, unrelated.token, 'PATCH', {
      ...completeProfile('First'), maritalStatus: 'married', childrenCount: 5,
      weightKg: 74, expectedVersion: 2,
    },
  );
  assert.equal(mutableProfile.profile.version, 3);
  assert.equal(mutableProfile.profile.weightKg, 74);
  const blockedPairRequest = await call<{ request: { id: string } }>(
    '/v1/requests', 201, unrelated.token, 'POST', {
      recipientUserId: second.user.id,
    },
  );
  await call('/v1/me/blocks', 403, admin.token);
  await call('/v1/me/blocks', 200, second.token, 'POST', { targetUserId: unrelated.user.id });
  const blockedList = await call<{ blocks: Array<{ userId: string }> }>('/v1/me/blocks', 200, second.token);
  assert(blockedList.blocks.some((item) => item.userId === unrelated.user.id));
  const blockedDiscovery = await call<{ profiles: Array<{ userId: string }> }>('/v1/discover', 200, second.token);
  assert(!blockedDiscovery.profiles.some((item) => item.userId === unrelated.user.id));
  await call('/v1/requests', 409, unrelated.token, 'POST', {
    recipientUserId: second.user.id,
  });
  await call(`/v1/requests/${blockedPairRequest.request.id}/decision`, 404, second.token, 'POST',
    { action: 'accept' });
  await call(`/v1/me/blocks/${unrelated.user.id}`, 200, second.token, 'DELETE');

  await call('/v1/me/profile', 409, first.token, 'PATCH', {
    ...completeProfile('First'), expectedVersion: 1,
  });

  await call('/v1/me/profile', 400, first.token, 'PATCH', {
    ...completeProfile('First'), preferredMinAge: 51, preferredMaxAge: 50, expectedVersion: 2,
  });

  await call('/v1/me/profile', 200, second.token, 'PATCH', {
    ...completeProfile('Second'), preferredCountry: 'France', expectedVersion: 2,
  });
  const mismatched = await call<{ profiles: Array<{ userId: string }> }>('/v1/discover', 200, first.token);
  assert(!mismatched.profiles.some((profile) => profile.userId === second.user.id));
  await call('/v1/requests', 409, first.token, 'POST', {
    recipientUserId: second.user.id,
  });
  await call('/v1/me/profile', 200, second.token, 'PATCH', {
    ...completeProfile('Second'), preferredCountry: 'Deutschlnd',
    partnerDescription: 'محب للفنون البحرية والاستكشاف في الطبيعة', expectedVersion: 3,
  });

  const discovery = await call<{ profiles: Array<{ userId: string; matchReasons: string[] }> }>(
    '/v1/discover', 200, first.token,
  );
  assert(discovery.profiles.some((profile) => profile.userId === second.user.id));
  assert(!discovery.profiles.some((profile) => profile.userId === first.user.id));
  assert(!discovery.profiles.find((profile) => profile.userId === second.user.id)?.matchReasons
    .some((reason) => reason.includes('تقارب واضح')));

  const sent = await call<{ request: { id: string; status: string } }>(
    '/v1/requests', 201, first.token, 'POST', {
      recipientUserId: second.user.id,
    },
  );
  assert.equal(sent.request.status, 'pending');
  await call('/v1/requests', 409, first.token, 'POST', {
    recipientUserId: second.user.id,
  });

  const inbox = await call<{ requests: Array<{ id: string; direction: string }> }>(
    '/v1/requests', 200, second.token,
  );
  assert(inbox.requests.some((item) => item.id === sent.request.id && item.direction === 'incoming'));
  const pendingWorkspace = await call<{ profile: { userId: string }; events: Array<{ type: string }> }>(
    `/v1/requests/${sent.request.id}/workspace`, 200, first.token,
  );
  assert.equal(pendingWorkspace.profile.userId, second.user.id);
  assert(pendingWorkspace.events.some((item) => item.type === 'request_sent'));
  await call(`/v1/requests/${sent.request.id}/decision`, 404, unrelated.token, 'POST', { action: 'accept' });
  await call(`/v1/requests/${sent.request.id}/decision`, 404, first.token, 'POST', { action: 'accept' });

  const accepted = await call<{ journey: { id: string } }>(
    `/v1/requests/${sent.request.id}/decision`, 200, second.token, 'POST', { action: 'accept' },
  );
  assert(accepted.journey.id);
  const acceptedWorkspace = await call<{ events: Array<{ type: string }> }>(
    `/v1/requests/${sent.request.id}/workspace`, 200, second.token,
  );
  assert(acceptedWorkspace.events.some((item) => item.type === 'request_accepted'));
  const discoveryAfterAcceptance = await call<{ profiles: Array<{ userId: string }> }>(
    '/v1/discover', 200, first.token,
  );
  assert(!discoveryAfterAcceptance.profiles.some((profile) => profile.userId === second.user.id));
  for (const session of [first, second]) {
    const result = await call<{ journeys: Array<{ id: string }> }>('/v1/journeys', 200, session.token);
    assert(result.journeys.some((journey) => journey.id === accepted.journey.id));
  }
  const strangerJourneys = await call<{ journeys: Array<{ id: string }> }>(
    '/v1/journeys', 200, unrelated.token,
  );
  assert(!strangerJourneys.journeys.some((journey) => journey.id === accepted.journey.id));
  await call(`/v1/requests/${sent.request.id}/decision`, 404, second.token, 'POST', { action: 'accept' });
  await call('/state', 410, first.token, 'PUT', { state: { version: 1 } });

  return { first, second, unrelated, representative, admin,
    accepted: { ...accepted, requestId: sent.request.id } };
};
