import { prisma } from "../database";
import type {
  House,
  HouseMembership,
  HouseInvitation,
  HouseRequest,
  HouseRole,
  InvitationStatus,
  RequestStatus,
  Prisma,
} from "@/prisma/generated/prisma/client";
import {
  ValidationError,
  NotFoundError,
  ConflictError,
  ForbiddenError,
} from "../errors";

const HOUSE_NAME_MIN_LENGTH = 3;
const HOUSE_NAME_MAX_LENGTH = 50;
const HOUSE_DESCRIPTION_MAX_LENGTH = 500;

export interface CreateHouseInput {
  name: string;
  description?: string | null;
  creatorId: string;
}

export interface UpdateHouseInput {
  name?: string;
  description?: string | null;
}

export interface InviteUserInput {
  houseId: string;
  inviterId: string;
  inviteeId: string;
}

export interface RequestToJoinInput {
  houseId: string;
  requesterId: string;
}

/**
 * Service de gestion des maisons
 */
export class HouseService {
  /**
   * Récupère une maison par son ID
   */
  async getHouseById(
    id: string,
    include?: Prisma.HouseInclude
  ): Promise<House | null> {
    return prisma.house.findUnique({
      where: { id },
      include,
    });
  }

  /**
   * Récupère toutes les maisons avec pagination
   */
  async getAllHouses(options?: {
    skip?: number;
    take?: number;
    include?: Prisma.HouseInclude;
    orderBy?: Prisma.HouseOrderByWithRelationInput;
  }): Promise<House[]> {
    return prisma.house.findMany({
      skip: options?.skip,
      take: options?.take,
      include: options?.include,
      orderBy: options?.orderBy || { createdAt: "desc" },
    });
  }

  /**
   * Recherche des maisons par nom
   */
  async searchHouses(
    searchTerm: string,
    options?: {
      skip?: number;
      take?: number;
      include?: Prisma.HouseInclude;
    }
  ): Promise<House[]> {
    return prisma.house.findMany({
      where: {
        name: {
          contains: searchTerm,
          mode: "insensitive",
        },
      },
      skip: options?.skip,
      take: options?.take,
      include: options?.include,
      orderBy: { createdAt: "desc" },
    });
  }

  /**
   * Récupère les maisons d'un utilisateur
   */
  async getUserHouses(
    userId: string,
    include?: Prisma.HouseInclude
  ): Promise<House[]> {
    const memberships = await prisma.houseMembership.findMany({
      where: { userId },
      include: {
        house: {
          include: include,
        },
      },
    });

    return memberships.map((m) => m.house);
  }

  /**
   * Récupère la maison d'un utilisateur (s'il en a une)
   */
  async getUserHouse(
    userId: string,
    include?: Prisma.HouseInclude
  ): Promise<House | null> {
    const membership = await prisma.houseMembership.findFirst({
      where: { userId },
      include: {
        house: {
          include: include,
        },
      },
      orderBy: { joinedAt: "asc" }, // Première maison jointe
    });

    return membership?.house || null;
  }

  /**
   * Vérifie si un utilisateur est membre d'une maison
   */
  async isUserMemberOfHouse(
    userId: string,
    houseId: string
  ): Promise<boolean> {
    const membership = await prisma.houseMembership.findUnique({
      where: {
        houseId_userId: {
          houseId,
          userId,
        },
      },
    });
    return !!membership;
  }

  /**
   * Vérifie si un utilisateur est propriétaire ou admin d'une maison
   */
  async isUserOwnerOrAdmin(
    userId: string,
    houseId: string
  ): Promise<boolean> {
    const membership = await prisma.houseMembership.findUnique({
      where: {
        houseId_userId: {
          houseId,
          userId,
        },
      },
    });

    return membership?.role === "OWNER" || membership?.role === "ADMIN";
  }

  /**
   * Vérifie si un utilisateur est propriétaire d'une maison
   */
  async isUserOwner(userId: string, houseId: string): Promise<boolean> {
    const membership = await prisma.houseMembership.findUnique({
      where: {
        houseId_userId: {
          houseId,
          userId,
        },
      },
    });

    return membership?.role === "OWNER";
  }

  /**
   * Récupère le rôle d'un utilisateur dans une maison
   */
  async getUserRole(
    userId: string,
    houseId: string
  ): Promise<HouseRole | null> {
    const membership = await prisma.houseMembership.findUnique({
      where: {
        houseId_userId: {
          houseId,
          userId,
        },
      },
    });

    return membership?.role || null;
  }

  /**
   * Récupère les membres d'une maison
   */
  async getHouseMembers(
    houseId: string,
    include?: Prisma.HouseMembershipInclude
  ): Promise<HouseMembership[]> {
    return prisma.houseMembership.findMany({
      where: { houseId },
      include,
      orderBy: [
        { role: "asc" }, // OWNER, ADMIN, MEMBER
        { joinedAt: "asc" },
      ],
    });
  }

  /**
   * Crée une nouvelle maison
   */
  async createHouse(data: CreateHouseInput): Promise<House> {
    // Validation
    if (!data.name || data.name.trim().length === 0) {
      throw new ValidationError("Le nom de la maison est requis", "name");
    }

    if (
      data.name.length < HOUSE_NAME_MIN_LENGTH ||
      data.name.length > HOUSE_NAME_MAX_LENGTH
    ) {
      throw new ValidationError(
        `Le nom de la maison doit contenir entre ${HOUSE_NAME_MIN_LENGTH} et ${HOUSE_NAME_MAX_LENGTH} caractères`,
        "name"
      );
    }

    if (data.description && data.description.length > HOUSE_DESCRIPTION_MAX_LENGTH) {
      throw new ValidationError(
        `La description ne peut pas dépasser ${HOUSE_DESCRIPTION_MAX_LENGTH} caractères`,
        "description"
      );
    }

    // Vérifier si l'utilisateur est déjà dans une maison
    const existingMembership = await prisma.houseMembership.findFirst({
      where: { userId: data.creatorId },
    });

    if (existingMembership) {
      throw new ConflictError(
        "Vous êtes déjà membre d'une maison. Vous devez quitter votre maison actuelle avant d'en créer une nouvelle."
      );
    }

    // Vérifier si le nom est déjà pris
    const existingHouse = await prisma.house.findFirst({
      where: {
        name: {
          equals: data.name.trim(),
          mode: "insensitive",
        },
      },
    });

    if (existingHouse) {
      throw new ConflictError("Ce nom de maison est déjà utilisé");
    }

    // Créer la maison et ajouter le créateur comme OWNER
    return prisma.house.create({
      data: {
        name: data.name.trim(),
        description: data.description?.trim() || null,
        creatorId: data.creatorId,
        memberships: {
          create: {
            userId: data.creatorId,
            role: "OWNER",
          },
        },
      },
    });
  }

  /**
   * Met à jour une maison
   */
  async updateHouse(
    houseId: string,
    userId: string,
    data: UpdateHouseInput
  ): Promise<House> {
    // Vérifier que l'utilisateur est propriétaire ou admin
    const isAuthorized = await this.isUserOwnerOrAdmin(userId, houseId);
    if (!isAuthorized) {
      throw new ForbiddenError(
        "Vous n'avez pas les permissions pour modifier cette maison"
      );
    }

    const updateData: Prisma.HouseUpdateInput = {};

    if (data.name !== undefined) {
      if (!data.name || data.name.trim().length === 0) {
        throw new ValidationError("Le nom de la maison est requis", "name");
      }

      if (
        data.name.length < HOUSE_NAME_MIN_LENGTH ||
        data.name.length > HOUSE_NAME_MAX_LENGTH
      ) {
        throw new ValidationError(
          `Le nom de la maison doit contenir entre ${HOUSE_NAME_MIN_LENGTH} et ${HOUSE_NAME_MAX_LENGTH} caractères`,
          "name"
        );
      }

      // Vérifier si le nom est déjà pris par une autre maison
      const existingHouse = await prisma.house.findFirst({
        where: {
          name: {
            equals: data.name.trim(),
            mode: "insensitive",
          },
          NOT: { id: houseId },
        },
      });

      if (existingHouse) {
        throw new ConflictError("Ce nom de maison est déjà utilisé");
      }

      updateData.name = data.name.trim();
    }

    if (data.description !== undefined) {
      if (data.description && data.description.length > HOUSE_DESCRIPTION_MAX_LENGTH) {
        throw new ValidationError(
          `La description ne peut pas dépasser ${HOUSE_DESCRIPTION_MAX_LENGTH} caractères`,
          "description"
        );
      }
      updateData.description = data.description?.trim() || null;
    }

    return prisma.house.update({
      where: { id: houseId },
      data: updateData,
    });
  }

  /**
   * Supprime une maison
   */
  async deleteHouse(houseId: string, userId: string): Promise<void> {
    // Vérifier que l'utilisateur est propriétaire
    const isOwner = await this.isUserOwner(userId, houseId);
    if (!isOwner) {
      throw new ForbiddenError(
        "Seul le propriétaire peut supprimer la maison"
      );
    }

    await prisma.house.delete({
      where: { id: houseId },
    });
  }

  /**
   * Invite un utilisateur à rejoindre une maison
   */
  async inviteUser(data: InviteUserInput): Promise<HouseInvitation> {
    // Vérifier que l'inviteur est membre de la maison
    const isMember = await this.isUserMemberOfHouse(
      data.inviterId,
      data.houseId
    );
    if (!isMember) {
      throw new ForbiddenError(
        "Vous devez être membre de la maison pour inviter quelqu'un"
      );
    }

    // Vérifier que l'invité n'est pas déjà membre
    const isAlreadyMember = await this.isUserMemberOfHouse(
      data.inviteeId,
      data.houseId
    );
    if (isAlreadyMember) {
      throw new ConflictError("Cet utilisateur est déjà membre de la maison");
    }

    // Vérifier qu'il n'y a pas déjà une invitation en attente
    const existingInvitation = await prisma.houseInvitation.findUnique({
      where: {
        houseId_inviteeId: {
          houseId: data.houseId,
          inviteeId: data.inviteeId,
        },
      },
    });

    if (existingInvitation && existingInvitation.status === "PENDING") {
      throw new ConflictError(
        "Une invitation est déjà en attente pour cet utilisateur"
      );
    }

    // Vérifier que l'invité n'est pas déjà dans une autre maison
    const existingMembership = await prisma.houseMembership.findFirst({
      where: { userId: data.inviteeId },
    });

    if (existingMembership) {
      throw new ConflictError(
        "Cet utilisateur est déjà membre d'une autre maison"
      );
    }

    // Créer l'invitation
    return prisma.houseInvitation.create({
      data: {
        houseId: data.houseId,
        inviterId: data.inviterId,
        inviteeId: data.inviteeId,
        status: "PENDING",
      },
    });
  }

  /**
   * Accepte une invitation
   */
  async acceptInvitation(
    invitationId: string,
    userId: string
  ): Promise<HouseMembership> {
    const invitation = await prisma.houseInvitation.findUnique({
      where: { id: invitationId },
    });

    if (!invitation) {
      throw new NotFoundError("Invitation");
    }

    if (invitation.inviteeId !== userId) {
      throw new ForbiddenError("Cette invitation ne vous est pas destinée");
    }

    if (invitation.status !== "PENDING") {
      throw new ConflictError("Cette invitation n'est plus valide");
    }

    // Vérifier que l'utilisateur n'est pas déjà dans une maison
    const existingMembership = await prisma.houseMembership.findFirst({
      where: { userId },
    });

    if (existingMembership) {
      throw new ConflictError(
        "Vous êtes déjà membre d'une maison. Vous devez quitter votre maison actuelle avant d'accepter cette invitation."
      );
    }

    // Créer le membership et mettre à jour l'invitation
    return prisma.$transaction(async (tx) => {
      const membership = await tx.houseMembership.create({
        data: {
          houseId: invitation.houseId,
          userId: invitation.inviteeId,
          role: "MEMBER",
        },
      });

      await tx.houseInvitation.update({
        where: { id: invitationId },
        data: { status: "ACCEPTED" },
      });

      // Annuler toutes les autres invitations en attente pour cet utilisateur
      await tx.houseInvitation.updateMany({
        where: {
          inviteeId: userId,
          status: "PENDING",
          id: { not: invitationId },
        },
        data: { status: "CANCELLED" },
      });

      // Annuler toutes les demandes en attente pour cet utilisateur
      await tx.houseRequest.updateMany({
        where: {
          requesterId: userId,
          status: "PENDING",
        },
        data: { status: "CANCELLED" },
      });

      return membership;
    });
  }

  /**
   * Refuse une invitation
   */
  async rejectInvitation(invitationId: string, userId: string): Promise<void> {
    const invitation = await prisma.houseInvitation.findUnique({
      where: { id: invitationId },
    });

    if (!invitation) {
      throw new NotFoundError("Invitation");
    }

    if (invitation.inviteeId !== userId) {
      throw new ForbiddenError("Cette invitation ne vous est pas destinée");
    }

    if (invitation.status !== "PENDING") {
      throw new ConflictError("Cette invitation n'est plus valide");
    }

    await prisma.houseInvitation.update({
      where: { id: invitationId },
      data: { status: "REJECTED" },
    });
  }

  /**
   * Annule une invitation (par l'inviteur)
   */
  async cancelInvitation(invitationId: string, userId: string): Promise<void> {
    const invitation = await prisma.houseInvitation.findUnique({
      where: { id: invitationId },
    });

    if (!invitation) {
      throw new NotFoundError("Invitation");
    }

    if (invitation.inviterId !== userId) {
      throw new ForbiddenError(
        "Vous ne pouvez annuler que vos propres invitations"
      );
    }

    if (invitation.status !== "PENDING") {
      throw new ConflictError("Cette invitation n'est plus valide");
    }

    await prisma.houseInvitation.update({
      where: { id: invitationId },
      data: { status: "CANCELLED" },
    });
  }

  /**
   * Demande à rejoindre une maison
   */
  async requestToJoin(data: RequestToJoinInput): Promise<HouseRequest> {
    // Vérifier que l'utilisateur n'est pas déjà membre
    const isMember = await this.isUserMemberOfHouse(
      data.requesterId,
      data.houseId
    );
    if (isMember) {
      throw new ConflictError("Vous êtes déjà membre de cette maison");
    }

    // Vérifier que l'utilisateur n'est pas déjà dans une autre maison
    const existingMembership = await prisma.houseMembership.findFirst({
      where: { userId: data.requesterId },
    });

    if (existingMembership) {
      throw new ConflictError(
        "Vous êtes déjà membre d'une maison. Vous devez quitter votre maison actuelle avant de faire une demande."
      );
    }

    // Vérifier qu'il n'y a pas déjà une demande en attente
    const existingRequest = await prisma.houseRequest.findUnique({
      where: {
        houseId_requesterId: {
          houseId: data.houseId,
          requesterId: data.requesterId,
        },
      },
    });

    if (existingRequest && existingRequest.status === "PENDING") {
      throw new ConflictError(
        "Une demande est déjà en attente pour cette maison"
      );
    }

    // Créer la demande
    return prisma.houseRequest.create({
      data: {
        houseId: data.houseId,
        requesterId: data.requesterId,
        status: "PENDING",
      },
    });
  }

  /**
   * Accepte une demande d'adhésion
   */
  async acceptRequest(
    requestId: string,
    userId: string
  ): Promise<HouseMembership> {
    const request = await prisma.houseRequest.findUnique({
      where: { id: requestId },
      include: { house: true },
    });

    if (!request) {
      throw new NotFoundError("Demande");
    }

    // Vérifier que l'utilisateur est propriétaire ou admin de la maison
    const isAuthorized = await this.isUserOwnerOrAdmin(
      userId,
      request.houseId
    );
    if (!isAuthorized) {
      throw new ForbiddenError(
        "Vous n'avez pas les permissions pour accepter cette demande"
      );
    }

    if (request.status !== "PENDING") {
      throw new ConflictError("Cette demande n'est plus valide");
    }

    // Vérifier que le demandeur n'est pas déjà dans une maison
    const existingMembership = await prisma.houseMembership.findFirst({
      where: { userId: request.requesterId },
    });

    if (existingMembership) {
      throw new ConflictError(
        "Cet utilisateur est déjà membre d'une autre maison"
      );
    }

    // Créer le membership et mettre à jour la demande
    return prisma.$transaction(async (tx) => {
      const membership = await tx.houseMembership.create({
        data: {
          houseId: request.houseId,
          userId: request.requesterId,
          role: "MEMBER",
        },
      });

      await tx.houseRequest.update({
        where: { id: requestId },
        data: { status: "ACCEPTED" },
      });

      // Annuler toutes les autres demandes en attente pour cet utilisateur
      await tx.houseRequest.updateMany({
        where: {
          requesterId: request.requesterId,
          status: "PENDING",
          id: { not: requestId },
        },
        data: { status: "CANCELLED" },
      });

      // Annuler toutes les invitations en attente pour cet utilisateur
      await tx.houseInvitation.updateMany({
        where: {
          inviteeId: request.requesterId,
          status: "PENDING",
        },
        data: { status: "CANCELLED" },
      });

      return membership;
    });
  }

  /**
   * Refuse une demande d'adhésion
   */
  async rejectRequest(requestId: string, userId: string): Promise<void> {
    const request = await prisma.houseRequest.findUnique({
      where: { id: requestId },
    });

    if (!request) {
      throw new NotFoundError("Demande");
    }

    // Vérifier que l'utilisateur est propriétaire ou admin de la maison
    const isAuthorized = await this.isUserOwnerOrAdmin(
      userId,
      request.houseId
    );
    if (!isAuthorized) {
      throw new ForbiddenError(
        "Vous n'avez pas les permissions pour refuser cette demande"
      );
    }

    if (request.status !== "PENDING") {
      throw new ConflictError("Cette demande n'est plus valide");
    }

    await prisma.houseRequest.update({
      where: { id: requestId },
      data: { status: "REJECTED" },
    });
  }

  /**
   * Annule une demande (par le demandeur)
   */
  async cancelRequest(requestId: string, userId: string): Promise<void> {
    const request = await prisma.houseRequest.findUnique({
      where: { id: requestId },
    });

    if (!request) {
      throw new NotFoundError("Demande");
    }

    if (request.requesterId !== userId) {
      throw new ForbiddenError(
        "Vous ne pouvez annuler que vos propres demandes"
      );
    }

    if (request.status !== "PENDING") {
      throw new ConflictError("Cette demande n'est plus valide");
    }

    await prisma.houseRequest.update({
      where: { id: requestId },
      data: { status: "CANCELLED" },
    });
  }

  /**
   * Quitte une maison
   */
  async leaveHouse(houseId: string, userId: string): Promise<void> {
    const membership = await prisma.houseMembership.findUnique({
      where: {
        houseId_userId: {
          houseId,
          userId,
        },
      },
    });

    if (!membership) {
      throw new NotFoundError("Membre");
    }

    // Le propriétaire ne peut pas quitter sa maison
    if (membership.role === "OWNER") {
      throw new ForbiddenError(
        "Le propriétaire ne peut pas quitter sa maison. Vous devez d'abord transférer la propriété ou supprimer la maison."
      );
    }

    await prisma.houseMembership.delete({
      where: {
        houseId_userId: {
          houseId,
          userId,
        },
      },
    });
  }

  /**
   * Récupère les invitations reçues par un utilisateur
   */
  async getUserInvitations(
    userId: string,
    status?: InvitationStatus
  ): Promise<HouseInvitation[]> {
    return prisma.houseInvitation.findMany({
      where: {
        inviteeId: userId,
        ...(status && { status }),
      },
      include: {
        house: true,
        inviter: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
      },
      orderBy: { createdAt: "desc" },
    });
  }

  /**
   * Récupère les demandes d'une maison
   */
  async getHouseRequests(
    houseId: string,
    status?: RequestStatus
  ): Promise<HouseRequest[]> {
    return prisma.houseRequest.findMany({
      where: {
        houseId,
        ...(status && { status }),
      },
      include: {
        requester: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
      },
      orderBy: { createdAt: "desc" },
    });
  }

  /**
   * Récupère les invitations envoyées par une maison
   */
  async getHouseInvitations(
    houseId: string,
    status?: InvitationStatus
  ): Promise<HouseInvitation[]> {
    return prisma.houseInvitation.findMany({
      where: {
        houseId,
        ...(status && { status }),
      },
      include: {
        invitee: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        inviter: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
      },
      orderBy: { createdAt: "desc" },
    });
  }

  /**
   * Récupère une invitation par son ID (avec seulement houseId)
   */
  async getInvitationById(
    id: string
  ): Promise<{ houseId: string } | null> {
    return prisma.houseInvitation.findUnique({
      where: { id },
      select: { houseId: true },
    });
  }
}

export const houseService = new HouseService();

