import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { ModuleAccessRepository } from '../../common/access/module-access.repository';
import type { AuthenticatedUser } from '../../common/auth/authenticated-user';
import type { CreateAccountShareDto } from './dto/create-account-share.dto';
import {
  AccountSharingRepository,
  type SharedModule,
} from './account-sharing.repository';

const STATE_KEYS: Record<SharedModule, string> = {
  trading: 'ck_trading_terminal_v10_30_live',
  portfolio: 'ck_portfolio_terminal_v6',
};

type AccountRecord = Record<string, unknown> & { id: string; name: string };

@Injectable()
export class AccountSharingService {
  constructor(
    private readonly repository: AccountSharingRepository,
    private readonly moduleAccess: ModuleAccessRepository,
  ) {}

  async catalog() {
    const [users, states, shares] = await Promise.all([
      this.repository.listUsers(),
      this.repository.listSourceStates(),
      this.repository.listShares(),
    ]);
    const identities = new Map(users.map((user) => [user.id, user] as const));
    const accounts = states.flatMap((state) => {
      const sharedModule = moduleForKey(state.key);
      if (!sharedModule) return [];
      const source = identities.get(state.ownerId);
      if (!source || source.role !== 'ADMIN') return [];
      return extractAccounts(state.value, sharedModule).map((account) => ({
        module: sharedModule,
        sourceOwnerId: source.id,
        sourceOwnerName: source.displayName,
        sourceOwnerEmail: source.email,
        sourceOwnerActive: source.isActive,
        sourceAccountId: account.id,
        sourceAccountName: account.name,
        grants: shares
          .filter((share) =>
            share.module === sharedModule &&
            share.sourceOwnerId === source.id &&
            share.sourceAccountId === account.id,
          )
          .map((share) => ({ shareId: share.id, granteeId: share.granteeId })),
      }));
    });

    return accounts.sort((left, right) =>
      left.module.localeCompare(right.module) ||
      left.sourceAccountName.localeCompare(right.sourceAccountName, 'de'),
    );
  }

  async create(input: CreateAccountShareDto, actor: AuthenticatedUser) {
    if (input.sourceOwnerId === input.granteeId) {
      throw new BadRequestException('An account owner cannot receive their own account as a share.');
    }

    const [users, states] = await Promise.all([
      this.repository.listUsers(),
      this.repository.listSourceStates(),
    ]);
    const target = users.find((user) => user.id === input.granteeId);
    const source = users.find((user) => user.id === input.sourceOwnerId);
    if (!target || !source) throw new NotFoundException('Source or target user not found.');
    if (!target.isActive) throw new BadRequestException('The target user is deactivated.');
    if (source.role !== 'ADMIN' || !source.isActive) {
      throw new BadRequestException('Managed accounts must belong to an active administrator.');
    }
    if (target.role === 'ADMIN') {
      throw new BadRequestException('Administrators do not need managed account shares.');
    }

    const sourceState = states.find((state) =>
      state.ownerId === input.sourceOwnerId && state.key === STATE_KEYS[input.module],
    );
    const account = extractAccounts(sourceState?.value, input.module)
      .find((item) => item.id === input.sourceAccountId);
    if (!account) throw new NotFoundException('The source account no longer exists.');

    const share = await this.repository.create({
      module: input.module,
      sourceOwnerId: input.sourceOwnerId,
      sourceAccountId: input.sourceAccountId,
      granteeId: input.granteeId,
      createdBy: actor.id,
    });

    const access = await this.moduleAccess.forUser(input.granteeId);
    if (access[input.module] === 'NONE') {
      await this.moduleAccess.set(input.granteeId, { [input.module]: 'READ' });
    }

    return { ...share, sourceAccountName: account.name };
  }

  async remove(id: string): Promise<void> {
    if (!(await this.repository.delete(id))) throw new NotFoundException('Share not found.');
  }

  async listFor(granteeId: string, module: SharedModule) {
    const shares = await this.repository.listSharesFor(granteeId, module);
    const sourceOwnerIds = [...new Set(shares.map((share) => share.sourceOwnerId))];
    const [users, states] = await Promise.all([
      this.repository.listUsersByIds(sourceOwnerIds),
      this.repository.listSourceStatesFor(sourceOwnerIds, STATE_KEYS[module]),
    ]);
    const identities = new Map(users.map((user) => [user.id, user] as const));

    return shares.flatMap((share) => {
      const source = identities.get(share.sourceOwnerId);
      if (!source || source.role !== 'ADMIN' || !source.isActive) return [];
      const sourceState = states.find((state) =>
        state.ownerId === share.sourceOwnerId && state.key === STATE_KEYS[module],
      );
      const account = extractAccounts(sourceState?.value, module)
        .find((item) => item.id === share.sourceAccountId);
      if (!account) return [];
      return [{
        ...account,
        id: `shared:${share.id}`,
        sharedAccess: {
          shareId: share.id,
          module,
          sourceOwnerId: share.sourceOwnerId,
          sourceOwnerName: source.displayName,
          sourceOwnerEmail: source.email,
          sourceAccountId: share.sourceAccountId,
          readOnly: true,
        },
      }];
    });
  }
}

function moduleForKey(key: string): SharedModule | null {
  if (key === STATE_KEYS.trading) return 'trading';
  if (key === STATE_KEYS.portfolio) return 'portfolio';
  return null;
}

function parseState(value: unknown): Record<string, unknown> | null {
  try {
    const parsed = typeof value === 'string' ? JSON.parse(value) : value;
    return parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : null;
  } catch {
    return null;
  }
}

function extractAccounts(value: unknown, module: SharedModule): AccountRecord[] {
  const state = parseState(value);
  const raw = module === 'trading' ? state?.accounts : state?.portfolios;
  if (!Array.isArray(raw)) return [];
  return raw.flatMap((item, index) => {
    if (!item || typeof item !== 'object') return [];
    const record = item as Record<string, unknown>;
    const id = typeof record.id === 'string' || typeof record.id === 'number'
      ? String(record.id).trim()
      : '';
    if (!id) return [];
    return [{
      ...record,
      id,
      name: typeof record.name === 'string' && record.name.trim()
        ? record.name
        : `${module === 'trading' ? 'Trading Account' : 'Portfolio'} ${index + 1}`,
    }];
  });
}
