import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../infra/prisma.service';
import { MediaService } from './media.service';
import { OAuthCoreService } from './oauth-core.service';
import { SessionService } from './session.service';
import { SettingsService } from './settings.service';
import { assertHumanAccount, HUMAN_USER_WHERE } from './system-account.util';

@Injectable()
export class FedcmService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly session: SessionService,
    private readonly oauthCore: OAuthCoreService,
    private readonly settings: SettingsService,
    private readonly media: MediaService
  ) {}

  async getAccounts(userId: string, sessionId: string) {
    await this.assertExistingSession(userId, sessionId);

    const user = await this.prisma.user.findFirst({
      where: { id: userId, deletedAt: null, status: 'ACTIVE', ...HUMAN_USER_WHERE }
    });
    if (!user) {
      return { accounts: [] };
    }
    assertHumanAccount(user, { asAuth: true });

    const consents = await this.prisma.oAuthConsent.findMany({
      where: { userId },
      include: { client: { select: { clientId: true, isActive: true } } }
    });

    const approvedClients = consents
      .filter((item) => item.client.isActive)
      .map((item) => item.client.clientId);

    const givenName = user.displayName.trim().split(/\s+/)[0] || user.displayName;
    const picture = await this.media.createFedcmAvatarUrl(user.id);
    const email = user.email?.trim() || undefined;
    const tel = user.phone?.trim() || undefined;

    return {
      accounts: [
        {
          id: user.id,
          name: user.displayName,
          given_name: givenName,
          email,
          picture,
          tel,
          approved_clients: approvedClients
        }
      ]
    };
  }

  async issueIdAssertion(userId: string, sessionId: string, clientId: string, accountId: string) {
    if (accountId !== userId) {
      throw new BadRequestException('account_id не совпадает с активной сессией');
    }

    await this.assertUnlockedSession(userId, sessionId);

    const client = await this.prisma.oAuthClient.findUnique({
      where: { clientId },
      include: { scopes: { include: { scope: true } } }
    });
    if (!client?.isActive) {
      throw new BadRequestException('OAuth-приложение не найдено или отключено');
    }

    const scopeSlugs = client.scopes.map((item) => item.scope.slug);
    const scope = scopeSlugs.length > 0 ? scopeSlugs.join(' ') : 'openid profile email';

    const consent = await this.oauthCore.checkConsent(userId, clientId, scope);
    if (!consent.granted) {
      await this.oauthCore.grantConsent(userId, clientId, scope);
    }

    const tokens = await this.oauthCore.issueFedcmTokens(userId, clientId, scopeSlugs.length > 0 ? scopeSlugs : ['openid', 'profile', 'email']);
    return { token: tokens.idToken };
  }

  async getClientMetadata(clientId: string) {
    const client = await this.prisma.oAuthClient.findUnique({ where: { clientId } });
    if (!client?.isActive) {
      throw new BadRequestException('OAuth-приложение не найдено или отключено');
    }

    const frontendUrl = (await this.settings.getValue('PUBLIC_FRONTEND_URL', 'http://localhost:3002')).replace(/\/+$/, '');
    const privacyFallback = `${frontendUrl}/data`;
    const privacy = (await this.settings.getValue('FEDCM_PRIVACY_POLICY_URL', '')).trim();
    const terms = (await this.settings.getValue('FEDCM_TERMS_URL', '')).trim();

    return {
      privacy_policy_url: privacy || privacyFallback,
      terms_of_service_url: terms || privacyFallback
    };
  }

  async isOneTapEnabled() {
    return this.settings.getBoolean('ONE_TAP_ENABLED', true);
  }

  private async assertExistingSession(userId: string, sessionId: string) {
    const state = await this.session.resolveSessionState(sessionId);
    if (!state || state.userId !== userId) {
      throw new UnauthorizedException('Сессия недействительна или истекла');
    }
  }

  private async assertUnlockedSession(userId: string, sessionId: string) {
    await this.assertExistingSession(userId, sessionId);
    const state = await this.session.resolveSessionState(sessionId);
    if (state?.requiresPin) {
      throw new UnauthorizedException('Требуется подтверждение PIN-кода');
    }
  }
}
