import { Injectable, ConflictException, NotFoundException, ForbiddenException } from '@nestjs/common';
import { PrismaService } from '@chatapp/database';
import { randomBytes } from 'crypto';

@Injectable()
export class WorkspacesService {
  constructor(private readonly prisma: PrismaService) {}

  async create(userId: string, dto: { name: string; domain: string }) {
    const existing = await this.prisma.workspace.findUnique({ where: { domain: dto.domain } });
    if (existing) throw new ConflictException('Domain already taken');

    const workspace = await this.prisma.workspace.create({
      data: {
        name: dto.name,
        domain: dto.domain,
        plan: 'free',
        maxMembers: 100,
        allowCreateChannels: true,
        allowCustomEmoji: true,
        allowSharedChannels: false,
        enforce2fa: false,
        ssoRequired: false,
        createdBy: userId,
        members: {
          create: { userId, role: 'owner', joinedAt: new Date() },
        },
        settings: {
          create: { defaultChannel: 'general', allowMessageDeletion: true, requireLinkPreviews: true },
        },
      },
    });
    return { id: workspace.id, name: workspace.name, domain: workspace.domain };
  }

  async findById(id: string) {
    const workspace = await this.prisma.workspace.findUnique({ where: { id } });
    if (!workspace) throw new NotFoundException('Workspace not found');
    return workspace;
  }

  async update(id: string, dto: any) {
    const workspace = await this.prisma.workspace.findUnique({ where: { id } });
    if (!workspace) throw new NotFoundException('Workspace not found');

    const data: any = {};
    if (dto.name) data.name = dto.name;
    if (dto.description !== undefined) data.description = dto.description;
    if (dto.logo_url !== undefined) data.logoUrl = dto.logo_url;
    if (dto.plan) data.plan = dto.plan;

    return this.prisma.workspace.update({ where: { id }, data });
  }

  async listMembers(wsId: string, query: any) {
    const limit = Math.min(parseInt(query.limit) || 50, 100);
    const offset = query.offset ? parseInt(query.offset) : 0;

    const [members, total] = await Promise.all([
      this.prisma.workspaceMember.findMany({
        where: { workspaceId: wsId, deactivatedAt: null },
        include: {
          user: { select: { id: true, displayName: true, avatarUrl: true, email: true, title: true, presence: true, lastActiveAt: true } },
        },
        take: limit,
        skip: offset,
        orderBy: { joinedAt: 'asc' },
      }),
      this.prisma.workspaceMember.count({
        where: { workspaceId: wsId, deactivatedAt: null },
      }),
    ]);

    return {
      members: members.map((m) => ({
        id: m.id,
        user_id: m.userId,
        display_name: m.user.displayName,
        avatar_url: m.user.avatarUrl,
        email: m.user.email,
        title: m.user.title,
        role: m.role,
        presence: m.user.presence,
        last_active_at: m.user.lastActiveAt,
        joined_at: m.joinedAt,
      })),
      total,
      limit,
      offset,
    };
  }

  async inviteMembers(wsId: string, invitedByUserId: string, dto: { emails: string[]; role?: string }) {
    if (!dto.emails || dto.emails.length === 0) {
      return { invitations: [] };
    }

    const role = dto.role || 'member';
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7-day expiry

    const invitations = await Promise.all(
      dto.emails.map(async (email) => {
        // Check if invitation already exists and is pending
        const existing = await this.prisma.workspaceInvitation.findFirst({
          where: {
            workspaceId: wsId,
            email,
            acceptedAt: null,
            expiresAt: { gt: new Date() },
          },
        });

        if (existing) {
          return {
            id: existing.id,
            email: existing.email,
            role: existing.role,
            status: 'already_invited',
            expires_at: existing.expiresAt,
          };
        }

        // Check if user is already a member
        const user = await this.prisma.user.findUnique({ where: { email } });
        if (user) {
          const member = await this.prisma.workspaceMember.findUnique({
            where: { workspaceId_userId: { workspaceId: wsId, userId: user.id } },
          });
          if (member && !member.deactivatedAt) {
            return { email, status: 'already_member' };
          }
        }

        const token = randomBytes(32).toString('hex');
        const invitation = await this.prisma.workspaceInvitation.create({
          data: {
            workspaceId: wsId,
            email,
            role,
            token,
            invitedBy: invitedByUserId,
            expiresAt,
          },
        });

        return {
          id: invitation.id,
          email: invitation.email,
          role: invitation.role,
          status: 'invited',
          expires_at: invitation.expiresAt,
        };
      }),
    );

    return { invitations };
  }

  async removeMember(wsId: string, userId: string, removedByUserId: string) {
    const member = await this.prisma.workspaceMember.findUnique({
      where: { workspaceId_userId: { workspaceId: wsId, userId } },
    });

    if (!member) throw new NotFoundException('Member not found');

    // Prevent removing the workspace owner
    if (member.role === 'owner') {
      throw new ForbiddenException('Cannot remove the workspace owner');
    }

    // Prevent self-removal
    if (userId === removedByUserId) {
      throw new ForbiddenException('Cannot remove yourself from the workspace');
    }

    await this.prisma.workspaceMember.update({
      where: { id: member.id },
      data: {
        deactivatedAt: new Date(),
        deactivatedBy: removedByUserId,
      },
    });

    // Also remove from all channel memberships
    await this.prisma.channelMember.updateMany({
      where: { userId, workspaceId: wsId, leftAt: null },
      data: { leftAt: new Date() },
    });

    return { removed: true, user_id: userId };
  }

  async updateMemberRole(wsId: string, userId: string, role: string) {
    const validRoles = ['owner', 'admin', 'member', 'guest'];
    if (!validRoles.includes(role)) {
      throw new ForbiddenException(`Invalid role. Must be one of: ${validRoles.join(', ')}`);
    }

    const member = await this.prisma.workspaceMember.findUnique({
      where: { workspaceId_userId: { workspaceId: wsId, userId } },
    });

    if (!member) throw new NotFoundException('Member not found');

    // Prevent changing the owner's role
    if (member.role === 'owner' && role !== 'owner') {
      throw new ForbiddenException('Cannot change the owner role. Transfer ownership first.');
    }

    const updated = await this.prisma.workspaceMember.update({
      where: { id: member.id },
      data: { role },
    });

    return {
      user_id: updated.userId,
      role: updated.role,
    };
  }

  async getAnalytics(wsId: string) {
    // Get total messages
    const totalMessages = await this.prisma.message.count({
      where: { workspaceId: wsId, isDeleted: false },
    });

    // Get total channels (non-archived)
    const totalChannels = await this.prisma.channel.count({
      where: { workspaceId: wsId, isArchived: false },
    });

    // Get total members (active)
    const totalMembers = await this.prisma.workspaceMember.count({
      where: { workspaceId: wsId, deactivatedAt: null },
    });

    // Get channels by type
    const channelsByType = await this.prisma.channel.groupBy({
      by: ['type'],
      where: { workspaceId: wsId, isArchived: false },
      _count: { id: true },
    });

    // Get members by role
    const membersByRole = await this.prisma.workspaceMember.groupBy({
      by: ['role'],
      where: { workspaceId: wsId, deactivatedAt: null },
      _count: { id: true },
    });

    // Get messages in the last 7 days
    const sevenDaysAgo = new Date();
    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
    const messagesLast7Days = await this.prisma.message.count({
      where: {
        workspaceId: wsId,
        isDeleted: false,
        createdAt: { gte: sevenDaysAgo },
      },
    });

    // Get active channels (with messages in last 7 days)
    const activeChannels = await this.prisma.channel.count({
      where: {
        workspaceId: wsId,
        isArchived: false,
        lastMessageAt: { gte: sevenDaysAgo },
      },
    });

    // Get total files uploaded
    const totalFiles = await this.prisma.file.count({
      where: { workspaceId: wsId, isDeleted: false },
    });

    return {
      workspace_id: wsId,
      totals: {
        messages: totalMessages,
        channels: totalChannels,
        members: totalMembers,
        files: totalFiles,
      },
      messages_last_7_days: messagesLast7Days,
      active_channels_last_7_days: activeChannels,
      channels_by_type: channelsByType.reduce((acc, c) => {
        acc[c.type] = c._count.id;
        return acc;
      }, {} as Record<string, number>),
      members_by_role: membersByRole.reduce((acc, m) => {
        acc[m.role] = m._count.id;
        return acc;
      }, {} as Record<string, number>),
    };
  }
}