import { prisma } from '@/services/core/database';
import {
  Task,
  TaskStatus,
  TaskPriority,
  TaskSource,
  BusinessError,
  DailyCheckbox,
  DailyCheckboxType,
  Tag,
} from '@/lib/types';
import { Prisma } from '@prisma/client';
import { getToday } from '@/lib/date-utils';
import { tagsService } from './tags';
import { getReadonlyFieldsForTask } from './readonly-fields';

/**
 * Service pour la gestion des tâches (version standalone)
 */
export class TasksService {
  /**
   * Récupère toutes les tâches avec filtres optionnels
   */
  async getTasks(
    userId: string,
    filters?: {
      status?: TaskStatus[];
      search?: string;
      limit?: number;
      offset?: number;
    }
  ): Promise<Task[]> {
    const where: Prisma.TaskWhereInput = {
      ownerId: userId, // Toujours filtrer par propriétaire
    };

    if (filters?.status) {
      where.status = { in: filters.status };
    }

    if (filters?.search) {
      where.OR = [
        { title: { contains: filters.search } },
        { description: { contains: filters.search } },
      ];
    }

    const tasks = await prisma.task.findMany({
      where,
      include: {
        taskTags: {
          include: {
            tag: true,
          },
        },
        primaryTag: true,
        _count: {
          select: {
            dailyCheckboxes: true,
          },
        },
      },
      take: filters?.limit, // Pas de limite par défaut - récupère toutes les tâches
      skip: filters?.offset || 0,
      orderBy: [
        { completedAt: 'desc' },
        { dueDate: 'asc' },
        { createdAt: 'desc' },
      ],
    });

    return tasks.map(this.mapPrismaTaskToTask);
  }

  /**
   * Crée une nouvelle tâche
   */
  async createTask(taskData: {
    title: string;
    description?: string;
    status?: TaskStatus;
    priority?: TaskPriority;
    tags?: string[];
    primaryTagId?: string;
    dueDate?: Date;
    ownerId: string; // Requis - chaque tâche doit avoir un propriétaire
  }): Promise<Task> {
    const status = taskData.status || 'todo';
    const task = await prisma.task.create({
      data: {
        title: taskData.title,
        description: taskData.description,
        status: status,
        priority: taskData.priority || 'medium',
        dueDate: taskData.dueDate,
        primaryTagId: taskData.primaryTagId,
        ownerId: taskData.ownerId, // Assigner le propriétaire
        source: 'manual', // Source manuelle
        sourceId: `manual-${Date.now()}`, // ID unique
        // Si créée directement en done/archived, définir completedAt
        completedAt:
          status === 'done' || status === 'archived' ? getToday() : null,
      },
      include: {
        taskTags: {
          include: {
            tag: true,
          },
        },
        primaryTag: true,
      },
    });

    // Créer les relations avec les tags
    if (taskData.tags && taskData.tags.length > 0) {
      await this.createTaskTagRelations(
        task.id,
        taskData.tags,
        taskData.ownerId
      );
    }

    // Récupérer la tâche avec les tags pour le retour
    const taskWithTags = await prisma.task.findUnique({
      where: { id: task.id },
      include: {
        taskTags: {
          include: {
            tag: true,
          },
        },
        primaryTag: true,
      },
    });

    return this.mapPrismaTaskToTask(taskWithTags!);
  }

  /**
   * Met à jour une tâche
   */
  async updateTask(
    userId: string,
    taskId: string,
    updates: {
      title?: string;
      description?: string;
      status?: TaskStatus;
      priority?: TaskPriority;
      tags?: string[];
      primaryTagId?: string;
      dueDate?: Date;
    }
  ): Promise<Task> {
    const task = await prisma.task.findFirst({
      where: { id: taskId, ownerId: userId },
    });

    if (!task) {
      throw new BusinessError(
        `Tâche ${taskId} introuvable ou accès non autorisé`
      );
    }

    // Logique métier : si on marque comme terminé, on ajoute la date
    const updateData: Prisma.TaskUpdateInput & { primaryTagId?: string } = {
      title: updates.title,
      description: updates.description,
      status: updates.status,
      priority: updates.priority,
      dueDate: updates.dueDate,
      primaryTagId: updates.primaryTagId,
      updatedAt: getToday(),
    };

    // Logique pour completedAt : date de résolution définitive
    if (updates.status === 'done') {
      // Première fois qu'on marque comme done -> définir completedAt
      updateData.completedAt = getToday();
    } else if (updates.status === 'archived' && !task.completedAt) {
      // Première fois qu'on archive -> définir completedAt (archiver = résoudre)
      updateData.completedAt = getToday();
    }
    // Si elle était déjà done/archived et qu'on change le statut,
    // on garde la completedAt existante (ne jamais l'effacer)

    await prisma.task.update({
      where: { id: taskId },
      data: updateData as Prisma.TaskUpdateInput,
    });

    // Mettre à jour les relations avec les tags
    if (updates.tags !== undefined) {
      await this.updateTaskTagRelations(taskId, updates.tags, userId);
    }

    // Récupérer la tâche avec les tags pour le retour
    const taskWithTags = await prisma.task.findUnique({
      where: { id: taskId },
      include: {
        taskTags: {
          include: {
            tag: true,
          },
        },
        primaryTag: true,
      },
    });

    return this.mapPrismaTaskToTask(taskWithTags!);
  }

  /**
   * Supprime une tâche
   */
  async deleteTask(userId: string, taskId: string): Promise<void> {
    const task = await prisma.task.findFirst({
      where: { id: taskId, ownerId: userId },
    });

    if (!task) {
      throw new BusinessError(
        `Tâche ${taskId} introuvable ou accès non autorisé`
      );
    }

    await prisma.task.delete({
      where: { id: taskId },
    });
  }

  /**
   * Met à jour le statut d'une tâche
   */
  async updateTaskStatus(
    userId: string,
    taskId: string,
    newStatus: TaskStatus
  ): Promise<Task> {
    return this.updateTask(userId, taskId, { status: newStatus });
  }

  /**
   * Récupère les daily checkboxes liées à une tâche
   */
  async getTaskRelatedCheckboxes(
    userId: string,
    taskId: string
  ): Promise<DailyCheckbox[]> {
    // Vérifier que la tâche appartient à l'utilisateur
    const task = await prisma.task.findFirst({
      where: { id: taskId, ownerId: userId },
    });

    if (!task) {
      throw new Error('Tâche non trouvée ou accès non autorisé');
    }

    const checkboxes = await prisma.dailyCheckbox.findMany({
      where: { taskId: taskId },
      include: { task: true },
      orderBy: [{ date: 'desc' }, { order: 'asc' }],
    });

    return checkboxes.map((checkbox) => ({
      id: checkbox.id,
      date: checkbox.date,
      text: checkbox.text,
      isChecked: checkbox.isChecked,
      type: checkbox.type as DailyCheckboxType,
      order: checkbox.order,
      taskId: checkbox.taskId ?? undefined,
      userId: checkbox.userId,
      task: checkbox.task
        ? {
            id: checkbox.task.id,
            title: checkbox.task.title,
            description: checkbox.task.description ?? undefined,
            status: checkbox.task.status as TaskStatus,
            priority: checkbox.task.priority as TaskPriority,
            source: checkbox.task.source as TaskSource,
            sourceId: checkbox.task.sourceId ?? undefined,
            tags: [], // Les tags ne sont pas nécessaires dans ce contexte
            dueDate: checkbox.task.dueDate ?? undefined,
            completedAt: checkbox.task.completedAt ?? undefined,
            createdAt: checkbox.task.createdAt,
            updatedAt: checkbox.task.updatedAt,
            jiraProject: checkbox.task.jiraProject ?? undefined,
            jiraKey: checkbox.task.jiraKey ?? undefined,
            jiraType: checkbox.task.jiraType ?? undefined,
            assignee: checkbox.task.assignee ?? undefined,
            ownerId: (checkbox.task as unknown as { ownerId: string }).ownerId, // Cast temporaire
          }
        : undefined,
      isArchived: checkbox.text.includes('[ARCHIVÉ]'),
      createdAt: checkbox.createdAt,
      updatedAt: checkbox.updatedAt,
    }));
  }

  /**
   * Récupère les statistiques des tâches
   */
  async getTaskStats(userId: string) {
    const [
      total,
      done,
      archived,
      inProgress,
      todo,
      backlog,
      cancelled,
      freeze,
    ] = await Promise.all([
      prisma.task.count({ where: { ownerId: userId } }),
      prisma.task.count({ where: { ownerId: userId, status: 'done' } }),
      prisma.task.count({ where: { ownerId: userId, status: 'archived' } }),
      prisma.task.count({ where: { ownerId: userId, status: 'in_progress' } }),
      prisma.task.count({ where: { ownerId: userId, status: 'todo' } }),
      prisma.task.count({ where: { ownerId: userId, status: 'backlog' } }),
      prisma.task.count({ where: { ownerId: userId, status: 'cancelled' } }),
      prisma.task.count({ where: { ownerId: userId, status: 'freeze' } }),
    ]);

    const completed = done + archived; // Terminées = Done + Archived

    return {
      total,
      completed,
      inProgress,
      todo,
      backlog,
      cancelled,
      freeze,
      archived,
      completionRate: total > 0 ? Math.round((completed / total) * 100) : 0,
    };
  }

  /**
   * Crée les relations TaskTag pour une tâche
   */
  private async createTaskTagRelations(
    taskId: string,
    tagNames: string[],
    userId: string
  ): Promise<void> {
    for (const tagName of tagNames) {
      try {
        // Utiliser le service tags pour créer ou récupérer le tag
        const tags = await tagsService.ensureTagsExist([tagName], userId);
        const tag = tags[0];

        // Créer la relation TaskTag si elle n'existe pas
        await prisma.taskTag.upsert({
          where: {
            taskId_tagId: {
              taskId: taskId,
              tagId: tag.id,
            },
          },
          update: {}, // Pas de mise à jour nécessaire
          create: {
            taskId: taskId,
            tagId: tag.id,
          },
        });
      } catch (error) {
        console.error(
          `Erreur lors de la création de la relation tag ${tagName}:`,
          error
        );
      }
    }
  }

  /**
   * Met à jour les relations TaskTag pour une tâche
   */
  private async updateTaskTagRelations(
    taskId: string,
    tagNames: string[],
    userId: string
  ): Promise<void> {
    // Supprimer toutes les relations existantes
    await prisma.taskTag.deleteMany({
      where: { taskId: taskId },
    });

    // Créer les nouvelles relations
    if (tagNames.length > 0) {
      await this.createTaskTagRelations(taskId, tagNames, userId);
    }
  }

  /**
   * Convertit une tâche Prisma en objet Task
   */
  private mapPrismaTaskToTask(
    prismaTask:
      | Prisma.TaskGetPayload<{
          include: {
            taskTags: {
              include: {
                tag: true;
              };
            };
            _count: {
              select: {
                dailyCheckboxes: true;
              };
            };
          };
        }>
      | Prisma.TaskGetPayload<object>
  ): Task {
    // Extraire les tags depuis les relations TaskTag
    let tags: string[] = [];
    let tagDetails: Tag[] = [];

    if (
      'taskTags' in prismaTask &&
      prismaTask.taskTags &&
      Array.isArray(prismaTask.taskTags)
    ) {
      // Utiliser les relations Prisma pour récupérer les noms et détails des tags
      tags = prismaTask.taskTags.map((tt) => tt.tag.name);
      tagDetails = prismaTask.taskTags.map((tt) => ({
        id: tt.tag.id,
        name: tt.tag.name,
        color: tt.tag.color,
        isPinned: tt.tag.isPinned,
      }));
    }

    // Extraire le count des todos
    let todosCount = 0;
    if ('_count' in prismaTask && prismaTask._count) {
      todosCount = prismaTask._count.dailyCheckboxes || 0;
    }

    // Déterminer les champs en lecture seule selon la source de synchronisation
    const readonlyFields = getReadonlyFieldsForTask(
      prismaTask.source as TaskSource,
      prismaTask.status as TaskStatus
    );

    return {
      id: prismaTask.id,
      title: prismaTask.title,
      description: prismaTask.description ?? undefined,
      status: prismaTask.status as TaskStatus,
      priority: prismaTask.priority as TaskPriority,
      source: prismaTask.source as TaskSource,
      sourceId: prismaTask.sourceId ?? undefined,
      tags: tags,
      tagDetails: tagDetails,
      primaryTagId: prismaTask.primaryTagId ?? undefined,
      primaryTag:
        'primaryTag' in prismaTask && prismaTask.primaryTag
          ? {
              id: (prismaTask.primaryTag as { id: string }).id,
              name: (prismaTask.primaryTag as { name: string }).name,
              color: (prismaTask.primaryTag as { color: string }).color,
              isPinned: (prismaTask.primaryTag as { isPinned: boolean })
                .isPinned,
            }
          : undefined,
      dueDate: prismaTask.dueDate ?? undefined,
      completedAt: prismaTask.completedAt ?? undefined,
      createdAt: prismaTask.createdAt,
      updatedAt: prismaTask.updatedAt,
      jiraProject: prismaTask.jiraProject ?? undefined,
      jiraKey: prismaTask.jiraKey ?? undefined,
      jiraType: prismaTask.jiraType ?? undefined,
      // Champs TFS
      tfsProject: prismaTask.tfsProject ?? undefined,
      tfsPullRequestId: prismaTask.tfsPullRequestId ?? undefined,
      tfsRepository: prismaTask.tfsRepository ?? undefined,
      tfsSourceBranch: prismaTask.tfsSourceBranch ?? undefined,
      tfsTargetBranch: prismaTask.tfsTargetBranch ?? undefined,
      assignee: prismaTask.assignee ?? undefined,
      ownerId: prismaTask.ownerId,
      todosCount: todosCount,
      readonlyFields: readonlyFields.length > 0 ? readonlyFields : undefined,
    };
  }
}

// Instance singleton
export const tasksService = new TasksService();
