import { prisma } from '../../config/database';
import { challengeScoringService } from '../../services/challenge-scoring.service';
import type { ChallengeStatus } from '@prisma/client';
import type { CreateChallengeInput, UpdateChallengeInput } from './challenge.schemas';

const USER_SELECT = { id: true, email: true, name: true } as const;

export const challengeService = {
  // ── Admin ──────────────────────────────────────────────────────────

  async create(data: CreateChallengeInput, userId: string) {
    return prisma.challenge.create({
      data: {
        title: data.title,
        description: data.description,
        metric: data.metric,
        startsAt: new Date(data.startsAt),
        endsAt: new Date(data.endsAt),
        minTeamSize: data.minTeamSize,
        maxTeamSize: data.maxTeamSize,
        maxTeams: data.maxTeams,
        createdByUserId: userId,
        status: 'DRAFT',
      },
    });
  },

  async update(id: string, data: UpdateChallengeInput) {
    const challenge = await prisma.challenge.findUnique({ where: { id } });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    if (challenge.status !== 'DRAFT' && challenge.status !== 'UPCOMING') {
      throw Object.assign(new Error('Can only edit DRAFT or UPCOMING challenges'), { statusCode: 400 });
    }

    return prisma.challenge.update({
      where: { id },
      data: {
        ...(data.title !== undefined && { title: data.title }),
        ...(data.description !== undefined && { description: data.description }),
        ...(data.metric !== undefined && { metric: data.metric }),
        ...(data.startsAt !== undefined && { startsAt: new Date(data.startsAt) }),
        ...(data.endsAt !== undefined && { endsAt: new Date(data.endsAt) }),
        ...(data.minTeamSize !== undefined && { minTeamSize: data.minTeamSize }),
        ...(data.maxTeamSize !== undefined && { maxTeamSize: data.maxTeamSize }),
        ...(data.maxTeams !== undefined && { maxTeams: data.maxTeams }),
      },
    });
  },

  async delete(id: string) {
    const challenge = await prisma.challenge.findUnique({ where: { id } });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    if (challenge.status !== 'DRAFT' && challenge.status !== 'CANCELLED') {
      throw Object.assign(new Error('Can only delete DRAFT or CANCELLED challenges'), { statusCode: 400 });
    }
    await prisma.challenge.delete({ where: { id } });
    return { success: true };
  },

  async activate(id: string) {
    const challenge = await prisma.challenge.findUnique({ where: { id } });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    if (challenge.status !== 'DRAFT') {
      throw Object.assign(new Error('Can only activate DRAFT challenges'), { statusCode: 400 });
    }

    const now = new Date();
    const newStatus: ChallengeStatus = challenge.startsAt <= now ? 'ACTIVE' : 'UPCOMING';

    return prisma.challenge.update({
      where: { id },
      data: { status: newStatus },
    });
  },

  async complete(id: string) {
    const challenge = await prisma.challenge.findUnique({ where: { id } });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    if (challenge.status !== 'ACTIVE') {
      throw Object.assign(new Error('Can only complete ACTIVE challenges'), { statusCode: 400 });
    }

    await challengeScoringService.scoreChallenge(id);
    return prisma.challenge.update({
      where: { id },
      data: { status: 'COMPLETED' },
    });
  },

  async cancel(id: string) {
    const challenge = await prisma.challenge.findUnique({ where: { id } });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    if (challenge.status === 'COMPLETED' || challenge.status === 'CANCELLED') {
      throw Object.assign(new Error('Challenge is already completed or cancelled'), { statusCode: 400 });
    }

    return prisma.challenge.update({
      where: { id },
      data: { status: 'CANCELLED' },
    });
  },

  async rescore(id: string) {
    const challenge = await prisma.challenge.findUnique({ where: { id } });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    await challengeScoringService.scoreChallenge(id);
    return { success: true };
  },

  // ── Queries ────────────────────────────────────────────────────────

  async findById(id: string) {
    const challenge = await prisma.challenge.findUnique({
      where: { id },
      include: {
        createdBy: { select: USER_SELECT },
        teams: {
          include: {
            captain: { select: USER_SELECT },
            members: {
              include: { user: { select: USER_SELECT } },
              orderBy: { score: 'desc' },
            },
          },
          orderBy: { score: 'desc' },
        },
      },
    });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    return challenge;
  },

  async listChallenges(page: number, limit: number, status?: ChallengeStatus) {
    const where = status ? { status } : {};
    const skip = (page - 1) * limit;

    const [challenges, total] = await Promise.all([
      prisma.challenge.findMany({
        where,
        include: {
          _count: { select: { teams: true } },
          createdBy: { select: USER_SELECT },
        },
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
      }),
      prisma.challenge.count({ where }),
    ]);

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

  async getLeaderboard(challengeId: string) {
    const teams = await prisma.challengeTeam.findMany({
      where: { challengeId },
      include: {
        captain: { select: USER_SELECT },
        members: {
          include: { user: { select: USER_SELECT } },
          orderBy: { score: 'desc' },
        },
      },
      orderBy: { score: 'desc' },
    });
    return { teams };
  },

  async getTeamDetail(teamId: string) {
    const team = await prisma.challengeTeam.findUnique({
      where: { id: teamId },
      include: {
        challenge: true,
        captain: { select: USER_SELECT },
        members: {
          include: { user: { select: USER_SELECT } },
          orderBy: { score: 'desc' },
        },
      },
    });
    if (!team) {
      throw Object.assign(new Error('Team not found'), { statusCode: 404 });
    }
    return team;
  },

  // ── User actions ───────────────────────────────────────────────────

  async createTeam(challengeId: string, userId: string, teamName: string) {
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
      include: { _count: { select: { teams: true } } },
    });
    if (!challenge) {
      throw Object.assign(new Error('Challenge not found'), { statusCode: 404 });
    }
    if (challenge.status !== 'UPCOMING' && challenge.status !== 'ACTIVE') {
      throw Object.assign(new Error('Challenge is not accepting teams'), { statusCode: 400 });
    }
    if (challenge.maxTeams && challenge._count.teams >= challenge.maxTeams) {
      throw Object.assign(new Error('Maximum number of teams reached'), { statusCode: 400 });
    }

    // Check user not already on a team for this challenge
    const existing = await prisma.challengeTeamMember.findFirst({
      where: { userId, team: { challengeId } },
    });
    if (existing) {
      throw Object.assign(new Error('You are already on a team for this challenge'), { statusCode: 409 });
    }

    const team = await prisma.challengeTeam.create({
      data: {
        challengeId,
        name: teamName,
        captainUserId: userId,
        members: {
          create: { userId },
        },
      },
      include: {
        captain: { select: USER_SELECT },
        members: { include: { user: { select: USER_SELECT } } },
      },
    });

    return team;
  },

  async joinTeam(teamId: string, userId: string) {
    const team = await prisma.challengeTeam.findUnique({
      where: { id: teamId },
      include: {
        challenge: true,
        _count: { select: { members: true } },
      },
    });
    if (!team) {
      throw Object.assign(new Error('Team not found'), { statusCode: 404 });
    }
    if (team.challenge.status !== 'UPCOMING' && team.challenge.status !== 'ACTIVE') {
      throw Object.assign(new Error('Challenge is not accepting members'), { statusCode: 400 });
    }
    if (team._count.members >= team.challenge.maxTeamSize) {
      throw Object.assign(new Error('Team is full'), { statusCode: 400 });
    }

    // Check user not already on another team for this challenge
    const existing = await prisma.challengeTeamMember.findFirst({
      where: { userId, team: { challengeId: team.challengeId } },
    });
    if (existing) {
      throw Object.assign(new Error('You are already on a team for this challenge'), { statusCode: 409 });
    }

    await prisma.challengeTeamMember.create({
      data: { teamId, userId },
    });

    return prisma.challengeTeam.findUnique({
      where: { id: teamId },
      include: {
        captain: { select: USER_SELECT },
        members: { include: { user: { select: USER_SELECT } } },
      },
    });
  },

  async leaveTeam(teamId: string, userId: string) {
    const member = await prisma.challengeTeamMember.findUnique({
      where: { teamId_userId: { teamId, userId } },
    });
    if (!member) {
      throw Object.assign(new Error('You are not on this team'), { statusCode: 404 });
    }

    const team = await prisma.challengeTeam.findUnique({
      where: { id: teamId },
      include: { _count: { select: { members: true } } },
    });
    if (!team) {
      throw Object.assign(new Error('Team not found'), { statusCode: 404 });
    }

    // If captain and last member, delete entire team
    if (team.captainUserId === userId && team._count.members === 1) {
      await prisma.challengeTeam.delete({ where: { id: teamId } });
      return { success: true, teamDeleted: true };
    }

    // Remove member
    await prisma.challengeTeamMember.delete({
      where: { teamId_userId: { teamId, userId } },
    });

    // If captain, transfer to next member
    if (team.captainUserId === userId) {
      const nextMember = await prisma.challengeTeamMember.findFirst({
        where: { teamId },
        orderBy: { joinedAt: 'asc' },
      });
      if (nextMember) {
        await prisma.challengeTeam.update({
          where: { id: teamId },
          data: { captainUserId: nextMember.userId },
        });
      }
    }

    return { success: true, teamDeleted: false };
  },

  async getMyTeam(challengeId: string, userId: string) {
    const member = await prisma.challengeTeamMember.findFirst({
      where: { userId, team: { challengeId } },
      include: {
        team: {
          include: {
            captain: { select: USER_SELECT },
            members: {
              include: { user: { select: USER_SELECT } },
              orderBy: { score: 'desc' },
            },
          },
        },
      },
    });
    return member?.team ?? null;
  },
};
