import { Injectable, UnauthorizedException } from '@nestjs/common';
import * as bcrypt from 'bcryptjs';
import { SessionStatus } from '../generated/prisma/client';
import { PrismaService } from '../infra/prisma.service';
import { AuthService } from './auth.service';
import { SettingsService } from './settings.service';
import { isProtectedSystemAccount } from './system-account.util';

export interface SessionState {
  id: string;
  userId: string;
  pinVerified: boolean;
  status: SessionStatus;
  requiresPin: boolean;
}

@Injectable()
export class SessionService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly settings: SettingsService,
    private readonly auth: AuthService
  ) {}

  async resolveSessionState(sessionId: string): Promise<SessionState | null> {
    const session = await this.prisma.session.findUnique({
      where: { id: sessionId },
      include: { user: { include: { pinCode: true } } }
    });

    if (!session || session.status === SessionStatus.REVOKED || session.expiresAt < new Date()) {
      return null;
    }

    if (isProtectedSystemAccount(session.user)) {
      await this.prisma.session.update({
        where: { id: sessionId },
        data: { status: SessionStatus.REVOKED }
      });
      return null;
    }

    const pinEnabled = Boolean(session.user.pinCode?.isEnabled);
    let pinVerified = session.pinVerified;
    let status = session.status;

    if (pinEnabled && pinVerified && status === SessionStatus.ACTIVE) {
      const timeoutMinutes = await this.settings.getNumber('PIN_LOCK_TIMEOUT_MINUTES', 15);
      const threshold = new Date(Date.now() - timeoutMinutes * 60_000);
      if (session.updatedAt < threshold) {
        await this.prisma.session.update({
          where: { id: sessionId },
          data: { pinVerified: false, status: SessionStatus.LOCKED }
        });
        pinVerified = false;
        status = SessionStatus.LOCKED;
      }
    }

    const requiresPin = pinEnabled && (!pinVerified || status === SessionStatus.LOCKED);

    return {
      id: session.id,
      userId: session.userId,
      pinVerified,
      status,
      requiresPin
    };
  }

  async touchSessionActivity(sessionId: string) {
    const state = await this.resolveSessionState(sessionId);
    if (!state || state.requiresPin) {
      return state;
    }

    await this.prisma.session.update({
      where: { id: sessionId },
      data: { updatedAt: new Date() }
    });

    return state;
  }

  async validateSession(userId: string, sessionId: string, touchActivity: boolean) {
    const state = touchActivity
      ? await this.touchSessionActivity(sessionId)
      : await this.resolveSessionState(sessionId);

    if (!state || state.userId !== userId) {
      throw new UnauthorizedException('Сессия недействительна');
    }

    return {
      requiresPin: state.requiresPin,
      sessionId: state.id,
      pinVerified: state.pinVerified
    };
  }

  async refreshSession(refreshToken: string, sessionId?: string) {
    const sessions = await this.prisma.session.findMany({
      where: {
        ...(sessionId ? { id: sessionId } : {}),
        status: { not: SessionStatus.REVOKED },
        expiresAt: { gt: new Date() }
      },
      include: { user: { include: { pinCode: true } } },
      orderBy: { updatedAt: 'desc' },
      take: sessionId ? 1 : 50
    });

    let matchedSession: (typeof sessions)[number] | null = null;
    for (const session of sessions) {
      const matches = await bcrypt.compare(refreshToken, session.refreshHash);
      if (matches) {
        matchedSession = session;
        break;
      }
    }

    if (!matchedSession) {
      throw new UnauthorizedException('Refresh token недействителен');
    }

    const state = await this.resolveSessionState(matchedSession.id);
    if (!state) {
      throw new UnauthorizedException('Сессия недействительна');
    }

    if (state.requiresPin) {
      const user = await this.auth.getMe(matchedSession.userId);
      return {
        requiresPin: true,
        sessionId: state.id,
        pinVerified: false,
        user
      };
    }

    await this.prisma.session.update({
      where: { id: matchedSession.id },
      data: { updatedAt: new Date() }
    });

    const user = await this.auth.getMe(matchedSession.userId);

    return {
      requiresPin: false,
      accessToken: await this.auth.issueAccessToken(matchedSession.user, matchedSession.id, true),
      sessionId: matchedSession.id,
      pinVerified: true,
      user
    };
  }

  async lockExpiredPinSessions() {
    const timeoutMinutes = await this.settings.getNumber('PIN_LOCK_TIMEOUT_MINUTES', 15);
    const threshold = new Date(Date.now() - timeoutMinutes * 60_000);
    return this.prisma.session.updateMany({
      where: {
        pinVerified: true,
        updatedAt: { lt: threshold },
        status: SessionStatus.ACTIVE,
        user: { pinCode: { isEnabled: true } }
      },
      data: {
        pinVerified: false,
        status: SessionStatus.LOCKED
      }
    });
  }
}
