import assert from 'node:assert/strict';
import test from 'node:test';

import {
  createWorkspace,
  deleteWorkspaceMember,
  deleteWorkspaceIfEmpty,
  ensureDefaultWorkspaceForUser,
  ensurePersonalWorkspaceForUser,
  findAlternateWorkspaceForUser,
  getPlatformAdminSummary,
  listOwnedWorkspacesByOwnerEmail,
  updateWorkspace,
  updateWorkspaceMemberRole,
} from './workspaceRepository.js';
import { mockDb } from '../test/mockDb.js';
import type { UserRow } from '../types.js';

const user: UserRow = {
  id: 'user-1',
  email: 'owner@example.com',
  password_hash: null,
  name: 'Owner',
  created_at: '2026-04-14T00:00:00.000Z',
};

test('ensurePersonalWorkspaceForUser returns an existing workspace without creating one', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [{ workspace_id: 42 }],
  });

  const workspaceId = await ensurePersonalWorkspaceForUser(user);

  assert.equal(workspaceId, 42);
  assert.equal(calls.length, 1);
  assert.match(calls[0].text, /FROM workspace_members/);
});

test('ensurePersonalWorkspaceForUser creates a fresh workspace instead of claiming the legacy seed flock', async () => {
  const { calls } = mockDb(
    {
      rowCount: 0,
      rows: [],
    },
    {
      rowCount: 1,
      rows: [{ next_id: 43 }],
    },
    {
      rowCount: 1,
      rows: [],
    },
    {
      rowCount: 1,
      rows: [],
    },
  );

  const workspaceId = await ensurePersonalWorkspaceForUser(user);

  assert.equal(workspaceId, 43);
  assert.equal(calls.length, 4);
  assert.match(calls[1].text, /SELECT COALESCE\(MAX\(id\), 0\) \+ 1 AS next_id FROM workspaces/);
  assert.match(calls[2].text, /INSERT INTO workspaces/);
  assert.match(calls[3].text, /INSERT INTO workspace_members/);
  assert.deepEqual(calls[2].params, [43, "Owner's Flock", 'owner@example.com']);
});

test('ensureDefaultWorkspaceForUser reuses an existing rescue workspace without creating a household flock', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [{ workspace_id: 84 }],
  });

  const workspaceId = await ensureDefaultWorkspaceForUser(user);

  assert.equal(workspaceId, 84);
  assert.equal(calls.length, 1);
  assert.match(calls[0].text, /FROM workspace_members/);
  assert.doesNotMatch(calls[0].text, /workspaces\.workspace_type = 'standard'/);
});

test('ensureDefaultWorkspaceForUser creates a household flock when the user has no workspace', async () => {
  const { calls } = mockDb(
    {
      rowCount: 0,
      rows: [],
    },
    {
      rowCount: 0,
      rows: [],
    },
    {
      rowCount: 1,
      rows: [{ next_id: 43 }],
    },
    {
      rowCount: 1,
      rows: [],
    },
    {
      rowCount: 1,
      rows: [],
    },
  );

  const workspaceId = await ensureDefaultWorkspaceForUser(user);

  assert.equal(workspaceId, 43);
  assert.equal(calls.length, 5);
  assert.match(calls[0].text, /FROM workspace_members/);
  assert.match(calls[1].text, /workspaces\.workspace_type = 'standard'/);
  assert.match(calls[3].text, /INSERT INTO workspaces/);
});

test('createWorkspace inserts owner membership and returns the created workspace', async () => {
  const { calls } = mockDb(
    { rowCount: 1, rows: [] },
    { rowCount: 1, rows: [] },
    {
      rowCount: 1,
      rows: [
        {
          id: 9,
          name: 'My Rescue',
          workspace_type: 'rescue',
          billing_email: 'billing@example.com',
          billing_plan: 'rescue_free',
          billing_interval: 'monthly',
          created_at: '2026-04-14T00:00:00.000Z',
          updated_at: '2026-04-14T00:00:00.000Z',
        },
      ],
    },
  );

  const workspace = await createWorkspace({
    id: 9,
    name: 'My Rescue',
    workspaceType: 'rescue',
    billingEmail: 'billing@example.com',
    billingPlan: 'rescue_free',
    billingInterval: 'monthly',
    owner: user,
  });

  assert.equal(workspace?.id, 9);
  assert.equal(calls.length, 3);
  assert.match(calls[0].text, /INSERT INTO workspaces/);
  assert.match(calls[1].text, /INSERT INTO workspace_members/);
  assert.match(calls[2].text, /SELECT id, name, workspace_type/);
});

test('updateWorkspace converts an existing household flock to rescue without inserting a new flock', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [
      {
        id: 42,
        name: 'Converted Rescue',
        workspace_type: 'rescue',
        billing_email: 'billing@example.com',
        billing_plan: 'rescue_free',
        billing_interval: 'monthly',
        subscription_status: 'active',
        rescue_verification_status: 'pending',
        created_at: '2026-04-14T00:00:00.000Z',
        updated_at: '2026-04-15T00:00:00.000Z',
      },
    ],
  });

  const workspace = await updateWorkspace({
    workspaceId: 42,
    name: 'Converted Rescue',
    workspaceType: 'rescue',
    billingEmail: 'billing@example.com',
    billingPlan: 'rescue_free',
    billingInterval: 'monthly',
  });

  assert.equal(workspace?.id, 42);
  assert.equal(workspace?.workspace_type, 'rescue');
  assert.equal(calls.length, 1);
  assert.match(calls[0].text, /UPDATE workspaces/);
  assert.doesNotMatch(calls[0].text, /INSERT INTO workspaces/);
  assert.deepEqual(calls[0].params, [42, 'Converted Rescue', 'rescue', 'billing@example.com', 'rescue_free', 'monthly']);
});

test('deleteWorkspaceIfEmpty blocks deletion when birds are still assigned', async () => {
  const { calls } = mockDb(
    {
      rowCount: 1,
      rows: [{ count: '2' }],
    },
  );

  const result = await deleteWorkspaceIfEmpty(42);

  assert.deepEqual(result, { deleted: false, reason: 'birds_present' });
  assert.equal(calls.length, 1);
  assert.match(calls[0].text, /FROM birds/);
});

test('deleteWorkspaceIfEmpty deletes an empty workspace', async () => {
  const { calls } = mockDb(
    {
      rowCount: 1,
      rows: [{ count: '0' }],
    },
    {
      rowCount: 1,
      rows: [{ id: 42 }],
    },
  );

  const result = await deleteWorkspaceIfEmpty(42);

  assert.deepEqual(result, { deleted: true });
  assert.equal(calls.length, 2);
  assert.match(calls[1].text, /DELETE FROM workspaces/);
});

test('findAlternateWorkspaceForUser returns another workspace when available', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [{ workspace_id: 84 }],
  });

  const workspaceId = await findAlternateWorkspaceForUser('user-1', 42);

  assert.equal(workspaceId, 84);
  assert.deepEqual(calls[0].params, ['user-1', 42]);
});

test('listOwnedWorkspacesByOwnerEmail resolves accepted owner flocks by email', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [
      {
        id: 84,
        name: 'Receiving Flock',
        workspace_type: 'standard',
        billing_email: 'receiver@example.com',
        billing_plan: 'household_basic',
        subscription_status: 'active',
        rescue_verification_status: 'not_required',
        created_at: '2026-04-14T00:00:00.000Z',
        updated_at: '2026-04-14T00:00:00.000Z',
      },
    ],
  });

  const workspaces = await listOwnedWorkspacesByOwnerEmail('Receiver@Example.com', 42);

  assert.equal(workspaces[0]?.id, 84);
  assert.deepEqual(calls[0].params, ['Receiver@Example.com', 42]);
  assert.match(calls[0].text, /workspace_members\.role = 'owner'/);
  assert.match(calls[0].text, /accepted_at IS NOT NULL/);
  assert.match(calls[0].text, /workspaces\.id <> \$2/);
});

test('updateWorkspaceMemberRole changes a non-owner member role', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [
      {
        id: 'member-1',
        workspace_id: 42,
        user_id: 'user-2',
        invite_email: 'helper@example.com',
        name: 'Helper',
        role: 'viewer',
        accepted_at: '2026-04-14T00:00:00.000Z',
        created_at: '2026-04-14T00:00:00.000Z',
      },
    ],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'member-1',
    workspaceId: 42,
    role: 'viewer',
    requesterMemberId: 'owner-member',
    requesterIsBillingOwner: false,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member?.role, 'viewer');
  assert.deepEqual(calls[0].params, ['member-1', 42, 'viewer', false, 'owner-member', 'billing@example.com', 'owner']);
  assert.match(calls[0].text, /UPDATE workspace_members/);
  assert.match(calls[0].text, /role <> 'owner'/);
});

test('updateWorkspaceMemberRole returns null when no non-owner member matches', async () => {
  mockDb({
    rowCount: 0,
    rows: [],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'owner-member',
    workspaceId: 42,
    role: 'viewer',
    requesterMemberId: 'owner-member',
    requesterIsBillingOwner: false,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member, null);
});

test('updateWorkspaceMemberRole lets the billing owner change another owner role', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [
      {
        id: 'other-owner',
        workspace_id: 42,
        user_id: 'user-2',
        invite_email: 'other@example.com',
        name: 'Other Owner',
        role: 'assistant',
        accepted_at: '2026-04-14T00:00:00.000Z',
        created_at: '2026-04-14T00:00:00.000Z',
      },
    ],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'other-owner',
    workspaceId: 42,
    role: 'assistant',
    requesterMemberId: 'billing-owner',
    requesterIsBillingOwner: true,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member?.role, 'assistant');
  assert.deepEqual(calls[0].params, ['other-owner', 42, 'assistant', true, 'billing-owner', 'billing@example.com', 'owner']);
  assert.match(calls[0].text, /id <> \$5/);
});

test('updateWorkspaceMemberRole does not let the billing owner change their own owner role', async () => {
  mockDb({
    rowCount: 0,
    rows: [],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'billing-owner',
    workspaceId: 42,
    role: 'assistant',
    requesterMemberId: 'billing-owner',
    requesterIsBillingOwner: true,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member, null);
});

test('updateWorkspaceMemberRole lets a non-billing owner change another non-billing owner role', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [
      {
        id: 'other-owner',
        workspace_id: 42,
        user_id: 'user-2',
        invite_email: 'other@example.com',
        name: 'Other Owner',
        role: 'assistant',
        accepted_at: '2026-04-14T00:00:00.000Z',
        created_at: '2026-04-14T00:00:00.000Z',
      },
    ],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'other-owner',
    workspaceId: 42,
    role: 'assistant',
    requesterMemberId: 'non-billing-owner',
    requesterIsBillingOwner: false,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member?.role, 'assistant');
  assert.deepEqual(calls[0].params, ['other-owner', 42, 'assistant', false, 'non-billing-owner', 'billing@example.com', 'owner']);
  assert.match(calls[0].text, /LOWER\(BTRIM\(COALESCE\(invite_email, email\)\)\) <> LOWER\(BTRIM\(\$6\)\)/);
});

test('updateWorkspaceMemberRole does not let a non-billing owner change the billing owner role', async () => {
  mockDb({
    rowCount: 0,
    rows: [],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'billing-owner',
    workspaceId: 42,
    role: 'assistant',
    requesterMemberId: 'non-billing-owner',
    requesterIsBillingOwner: false,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member, null);
});

test('updateWorkspaceMemberRole lets the billing owner promote a non-owner to owner', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [
      {
        id: 'member-1',
        workspace_id: 42,
        user_id: 'user-2',
        invite_email: 'helper@example.com',
        name: 'Helper',
        role: 'owner',
        accepted_at: '2026-04-14T00:00:00.000Z',
        created_at: '2026-04-14T00:00:00.000Z',
      },
    ],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'member-1',
    workspaceId: 42,
    role: 'owner',
    requesterMemberId: 'billing-owner',
    requesterIsBillingOwner: true,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member?.role, 'owner');
  assert.deepEqual(calls[0].params, ['member-1', 42, 'owner', true, 'billing-owner', 'billing@example.com', 'owner']);
  assert.match(calls[0].text, /\$3 <> 'owner'/);
});

test('updateWorkspaceMemberRole does not let a non-billing owner promote a member to owner', async () => {
  mockDb({
    rowCount: 0,
    rows: [],
  });

  const member = await updateWorkspaceMemberRole({
    memberId: 'member-1',
    workspaceId: 42,
    role: 'owner',
    requesterMemberId: 'non-billing-owner',
    requesterIsBillingOwner: false,
    requesterRole: 'owner',
    billingEmail: 'billing@example.com',
  });

  assert.equal(member, null);
});

test('deleteWorkspaceMember removes non-owner members without billing owner access', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [{ id: 'member-1' }],
  });

  const deleted = await deleteWorkspaceMember({
    memberId: 'member-1',
    workspaceId: 42,
    requesterMemberId: 'owner-member',
    requesterIsBillingOwner: false,
  });

  assert.equal(deleted, true);
  assert.deepEqual(calls[0].params, ['member-1', 42, false, 'owner-member']);
  assert.match(calls[0].text, /role <> 'owner'/);
});

test('deleteWorkspaceMember lets the billing owner remove another owner', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [{ id: 'other-owner' }],
  });

  const deleted = await deleteWorkspaceMember({
    memberId: 'other-owner',
    workspaceId: 42,
    requesterMemberId: 'billing-owner',
    requesterIsBillingOwner: true,
  });

  assert.equal(deleted, true);
  assert.deepEqual(calls[0].params, ['other-owner', 42, true, 'billing-owner']);
  assert.match(calls[0].text, /id <> \$4/);
});

test('deleteWorkspaceMember does not let the billing owner remove their own owner membership', async () => {
  mockDb({
    rowCount: 0,
    rows: [],
  });

  const deleted = await deleteWorkspaceMember({
    memberId: 'billing-owner',
    workspaceId: 42,
    requesterMemberId: 'billing-owner',
    requesterIsBillingOwner: true,
  });

  assert.equal(deleted, false);
});

test('getPlatformAdminSummary counts memorialized birds separately', async () => {
  const { calls } = mockDb({
    rowCount: 1,
    rows: [
      {
        total_birds: 8,
        memorialized_birds: 3,
        total_users: 4,
        total_workspaces: 2,
        rescue_workspaces: 1,
        rescue_birds: 5,
        pending_rescues: 1,
        daily_users: 2,
      },
    ],
  });

  const summary = await getPlatformAdminSummary();

  assert.equal(summary?.memorialized_birds, 3);
  assert.equal(calls.length, 1);
  assert.match(calls[0].text, /memorialized_birds/);
  assert.match(calls[0].text, /memorialized_at IS NOT NULL/);
  assert.match(calls[0].text, /rescue_birds/);
  assert.match(calls[0].text, /workspaces\.workspace_type = 'rescue'/);
});
