import { Repository, IsNull, LessThan } from 'typeorm';
import { Session, SessionStatus } from '../entities';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreateSessionDto {
  userId: string;
  deviceId?: string;
  ipAddress?: string;
  userAgent?: string;
  expiresAt?: Date;
}

export interface SessionFilters {
  userId?: string;
  deviceId?: string;
  status?: SessionStatus;
  isActive?: boolean;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export class SessionService {
  constructor(private readonly sessionRepository: Repository<Session>) {}

  async create(ctx: ServiceContext, data: CreateSessionDto): Promise<Session> {
    const expiresAt = data.expiresAt || new Date(Date.now() + 24 * 60 * 60 * 1000); // 24h default

    const session = this.sessionRepository.create({
      tenantId: ctx.tenantId,
      userId: data.userId,
      deviceId: data.deviceId,
      ipAddress: data.ipAddress,
      userAgent: data.userAgent,
      status: SessionStatus.ACTIVE,
      expiresAt,
      lastActivityAt: new Date(),
      createdBy: ctx.userId ?? null,
    });

    return this.sessionRepository.save(session);
  }

  async findById(ctx: ServiceContext, id: string): Promise<Session | null> {
    return this.sessionRepository.findOne({
      where: { id, tenantId: ctx.tenantId },
      relations: ['user', 'device'],
    });
  }

  async findActiveByUserId(ctx: ServiceContext, userId: string): Promise<Session[]> {
    return this.sessionRepository.find({
      where: {
        tenantId: ctx.tenantId,
        userId,
        status: SessionStatus.ACTIVE,
      },
      relations: ['device'],
      order: { lastActivityAt: 'DESC' },
    });
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: SessionFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<Session>> {
    const qb = this.sessionRepository
      .createQueryBuilder('session')
      .leftJoinAndSelect('session.user', 'user')
      .leftJoinAndSelect('session.device', 'device')
      .where('session.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.userId) {
      qb.andWhere('session.user_id = :userId', { userId: filters.userId });
    }

    if (filters.deviceId) {
      qb.andWhere('session.device_id = :deviceId', { deviceId: filters.deviceId });
    }

    if (filters.status) {
      qb.andWhere('session.status = :status', { status: filters.status });
    }

    if (filters.isActive) {
      qb.andWhere('session.status = :activeStatus', { activeStatus: SessionStatus.ACTIVE });
      qb.andWhere('session.expires_at > :now', { now: new Date() });
    }

    const [data, total] = await qb
      .orderBy('session.last_activity_at', 'DESC')
      .skip((page - 1) * limit)
      .take(limit)
      .getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async updateActivity(ctx: ServiceContext, sessionId: string): Promise<Session | null> {
    const session = await this.findById(ctx, sessionId);
    if (!session || session.status !== SessionStatus.ACTIVE) return null;

    session.lastActivityAt = new Date();
    return this.sessionRepository.save(session);
  }

  async invalidate(ctx: ServiceContext, sessionId: string): Promise<boolean> {
    const session = await this.findById(ctx, sessionId);
    if (!session) return false;

    session.status = SessionStatus.INVALIDATED;
    session.updatedBy = ctx.userId ?? null;
    await this.sessionRepository.save(session);

    return true;
  }

  async invalidateAllUserSessions(ctx: ServiceContext, userId: string): Promise<number> {
    const result = await this.sessionRepository.update(
      {
        tenantId: ctx.tenantId,
        userId,
        status: SessionStatus.ACTIVE,
      },
      {
        status: SessionStatus.INVALIDATED,
        updatedBy: ctx.userId ?? null,
      },
    );

    return result.affected ?? 0;
  }

  async invalidateAllExcept(ctx: ServiceContext, userId: string, keepSessionId: string): Promise<number> {
    const sessions = await this.sessionRepository.find({
      where: {
        tenantId: ctx.tenantId,
        userId,
        status: SessionStatus.ACTIVE,
      },
    });

    let count = 0;
    for (const session of sessions) {
      if (session.id !== keepSessionId) {
        session.status = SessionStatus.INVALIDATED;
        session.updatedBy = ctx.userId ?? null;
        await this.sessionRepository.save(session);
        count++;
      }
    }

    return count;
  }

  async isSessionValid(ctx: ServiceContext, sessionId: string): Promise<boolean> {
    const session = await this.findById(ctx, sessionId);
    if (!session) return false;

    if (session.status !== SessionStatus.ACTIVE) return false;
    if (session.expiresAt && session.expiresAt < new Date()) return false;

    return true;
  }

  async cleanupExpiredSessions(ctx: ServiceContext): Promise<number> {
    const result = await this.sessionRepository.update(
      {
        tenantId: ctx.tenantId,
        status: SessionStatus.ACTIVE,
        expiresAt: LessThan(new Date()),
      },
      {
        status: SessionStatus.EXPIRED,
      },
    );

    return result.affected ?? 0;
  }

  async getActiveSessionCount(ctx: ServiceContext, userId: string): Promise<number> {
    return this.sessionRepository.count({
      where: {
        tenantId: ctx.tenantId,
        userId,
        status: SessionStatus.ACTIVE,
      },
    });
  }

  async getSessionStats(ctx: ServiceContext, userId: string): Promise<{
    total: number;
    active: number;
    expired: number;
    invalidated: number;
  }> {
    const sessions = await this.sessionRepository.find({
      where: { tenantId: ctx.tenantId, userId },
    });

    return {
      total: sessions.length,
      active: sessions.filter(s => s.status === SessionStatus.ACTIVE).length,
      expired: sessions.filter(s => s.status === SessionStatus.EXPIRED).length,
      invalidated: sessions.filter(s => s.status === SessionStatus.INVALIDATED).length,
    };
  }
}
