import { protectedProcedure, TRPCError } from './router';
import { z } from 'zod';
import { eq, and, asc } from 'drizzle-orm';
import type { DrizzleDB } from '../../src/db/config/migrations';
import { teams, teamMembers } from '../../src/db/schema';

async function verifyTeamOwnership(
  db: DrizzleDB,
  teamId: string,
  userId: number
) {
  const teamRows = await db.select({ id: teams.id, ownerId: teams.ownerId })
    .from(teams)
    .where(eq(teams.id, teamId));

  const team = teamRows[0];
  if (!team) {
    throw new TRPCError({ code: 'NOT_FOUND', message: `Team ${teamId} not found` });
  }
  if (team.ownerId !== userId) {
    const memberRows = await db.select()
      .from(teamMembers)
      .where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId)));
    if (memberRows.length === 0) {
      throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to team ${teamId}` });
    }
  }
  return team;
}

async function verifyTeamRole(
  db: DrizzleDB,
  teamId: string,
  userId: number,
  allowedRoles: string[]
) {
  await verifyTeamOwnership(db, teamId, userId);

  const teamRows = await db.select({ id: teams.id, ownerId: teams.ownerId })
    .from(teams)
    .where(eq(teams.id, teamId));
  const team = teamRows[0];
  if (!team) return;

  if (team.ownerId === userId) return;

  const memberRows = await db.select()
    .from(teamMembers)
    .where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId)));

  const member = memberRows[0];
  if (!member || !allowedRoles.includes(member.role)) {
    throw new TRPCError({ code: 'FORBIDDEN', message: 'Insufficient permissions' });
  }
}

async function generateTeamId(): Promise<string> {
  const { randomUUID } = await import('crypto');
  return `team_${randomUUID()}`;
}

export const teamRouter = {
  // Team CRUD
  listTeams: protectedProcedure.query(async ({ ctx }) => {
    const owned = await ctx.db!.select()
      .from(teams)
      .where(eq(teams.ownerId, ctx.userId!))
      .orderBy(asc(teams.createdAt));

    const memberRows = await ctx.db!.select({ teamId: teamMembers.teamId })
      .from(teamMembers)
      .where(eq(teamMembers.userId, ctx.userId!));

    const memberTeamIds = new Set(memberRows.map((r) => r.teamId));
    const memberTeams: typeof owned = [];

    for (const tid of memberTeamIds) {
      const row = await ctx.db!.select()
        .from(teams)
        .where(eq(teams.id, tid))
        .then((r) => r[0]);
      if (row) memberTeams.push(row);
    }

    const all = [...owned, ...memberTeams];
    const seen = new Set(all.map((t) => t.id));
    return all.filter((t) => seen.has(t.id));
  }),

  getTeam: protectedProcedure
    .input(z.object({ id: z.string().min(1) }))
    .query(async ({ input, ctx }) => {
      await verifyTeamOwnership(ctx.db!, input.id, ctx.userId!);
      const rows = await ctx.db!.select()
        .from(teams)
        .where(eq(teams.id, input.id));
      return rows[0];
    }),

  createTeam: protectedProcedure
    .input(z.object({
      name: z.string().min(1).max(255),
    }))
    .mutation(async ({ input, ctx }) => {
      const teamId = await generateTeamId();
      const result = await ctx.db!.insert(teams)
        .values({
          id: teamId,
          name: input.name,
          ownerId: ctx.userId!,
        })
        .returning();

      const team = result[0];
      if (!team) {
        throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to create team' });
      }

      await ctx.db!.insert(teamMembers)
        .values({
          teamId: team.id,
          userId: ctx.userId!,
          role: 'owner',
        });

      return team;
    }),

  updateTeam: protectedProcedure
    .input(z.object({
      id: z.string().min(1),
      name: z.string().min(1).max(255).optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyTeamRole(ctx.db!, input.id, ctx.userId!, ['owner', 'admin']);

      const updateData: Record<string, any> = { updatedAt: new Date() };
      if (input.name !== undefined) updateData.name = input.name;

      const result = await ctx.db!.update(teams)
        .set(updateData)
        .where(eq(teams.id, input.id))
        .returning();
      return result[0];
    }),

  deleteTeam: protectedProcedure
    .input(z.object({ id: z.string().min(1) }))
    .mutation(async ({ input, ctx }) => {
      await verifyTeamOwnership(ctx.db!, input.id, ctx.userId!);

      const teamRows = await ctx.db!.select({ id: teams.id, ownerId: teams.ownerId })
        .from(teams)
        .where(eq(teams.id, input.id));
      if (teamRows[0]?.ownerId !== ctx.userId!) {
        throw new TRPCError({ code: 'FORBIDDEN', message: 'Only the owner can delete a team' });
      }

      await ctx.db!.delete(teamMembers)
        .where(eq(teamMembers.teamId, input.id));

      await ctx.db!.delete(teams)
        .where(eq(teams.id, input.id));

      return { success: true };
    }),

  // Team member management
  listMembers: protectedProcedure
    .input(z.object({ teamId: z.string().min(1) }))
    .query(async ({ input, ctx }) => {
      await verifyTeamOwnership(ctx.db!, input.teamId, ctx.userId!);
      return await ctx.db!.select()
        .from(teamMembers)
        .where(eq(teamMembers.teamId, input.teamId))
        .orderBy(asc(teamMembers.joinedAt));
    }),

  addMember: protectedProcedure
    .input(z.object({
      teamId: z.string().min(1),
      userId: z.number().int().positive(),
      role: z.enum(['owner', 'admin', 'editor', 'viewer']).default('editor'),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyTeamRole(ctx.db!, input.teamId, ctx.userId!, ['owner', 'admin']);

      const existing = await ctx.db!.select()
        .from(teamMembers)
        .where(and(eq(teamMembers.teamId, input.teamId), eq(teamMembers.userId, input.userId)));

      if (existing.length > 0) {
        throw new TRPCError({ code: 'CONFLICT', message: 'User is already a member of this team' });
      }

      const result = await ctx.db!.insert(teamMembers)
        .values({
          teamId: input.teamId,
          userId: input.userId,
          role: input.role,
        })
        .returning();
      return result[0];
    }),

  updateMemberRole: protectedProcedure
    .input(z.object({
      teamId: z.string().min(1),
      userId: z.number().int().positive(),
      role: z.enum(['owner', 'admin', 'editor', 'viewer']),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyTeamRole(ctx.db!, input.teamId, ctx.userId!, ['owner']);

      const result = await ctx.db!.update(teamMembers)
        .set({ role: input.role })
        .where(and(eq(teamMembers.teamId, input.teamId), eq(teamMembers.userId, input.userId)))
        .returning();
      return result[0];
    }),

  removeMember: protectedProcedure
    .input(z.object({
      teamId: z.string().min(1),
      userId: z.number().int().positive(),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyTeamRole(ctx.db!, input.teamId, ctx.userId!, ['owner', 'admin']);

      if (input.userId === ctx.userId!) {
        throw new TRPCError({ code: 'FORBIDDEN', message: 'You cannot remove yourself from this team' });
      }

      const memberRows = await ctx.db!.select()
        .from(teamMembers)
        .where(and(eq(teamMembers.teamId, input.teamId), eq(teamMembers.userId, input.userId)));

      if (memberRows[0]?.role === 'owner') {
        throw new TRPCError({ code: 'FORBIDDEN', message: 'Cannot remove the team owner' });
      }

      await ctx.db!.delete(teamMembers)
        .where(and(eq(teamMembers.teamId, input.teamId), eq(teamMembers.userId, input.userId)));

      return { success: true };
    }),

  leaveTeam: protectedProcedure
    .input(z.object({ teamId: z.string().min(1) }))
    .mutation(async ({ input, ctx }) => {
      const memberRows = await ctx.db!.select()
        .from(teamMembers)
        .where(and(eq(teamMembers.teamId, input.teamId), eq(teamMembers.userId, ctx.userId!)));

      if (memberRows[0]?.role === 'owner') {
        throw new TRPCError({ code: 'FORBIDDEN', message: 'Owner cannot leave the team. Transfer ownership first.' });
      }

      await ctx.db!.delete(teamMembers)
        .where(and(eq(teamMembers.teamId, input.teamId), eq(teamMembers.userId, ctx.userId!)));

      return { success: true };
    }),
};
