import { prisma } from '@/services/core/database';
import { Prisma } from '@prisma/client';
import {
  DailyCheckbox,
  DailyView,
  CreateDailyCheckboxData,
  UpdateDailyCheckboxData,
  BusinessError,
  DailyCheckboxType,
  TaskStatus,
  TaskPriority,
  TaskSource,
  Task,
} from '@/lib/types';
import {
  getPreviousWorkday,
  normalizeDate,
  formatDateForAPI,
  getToday,
  getYesterday,
} from '@/lib/date-utils';

/**
 * Service pour la gestion des checkboxes daily
 */
export class DailyService {
  /**
   * Récupère la vue daily pour une date donnée (checkboxes d'hier et d'aujourd'hui)
   */
  async getDailyView(date: Date, userId: string): Promise<DailyView> {
    // Normaliser la date (début de journée)
    const today = normalizeDate(date);

    // Utiliser la logique de jour de travail précédent au lieu de jour-1
    const yesterday = getPreviousWorkday(today);

    // Récupérer les checkboxes des deux jours
    const [yesterdayCheckboxes, todayCheckboxes] = await Promise.all([
      this.getCheckboxesByDate(yesterday, userId),
      this.getCheckboxesByDate(today, userId),
    ]);

    return {
      date: today,
      yesterday: yesterdayCheckboxes,
      today: todayCheckboxes,
    };
  }

  /**
   * Récupère toutes les checkboxes d'une date donnée pour un utilisateur
   */
  async getCheckboxesByDate(
    date: Date,
    userId: string
  ): Promise<DailyCheckbox[]> {
    // Normaliser la date (début de journée)
    const normalizedDate = normalizeDate(date);

    const checkboxes = await prisma.dailyCheckbox.findMany({
      where: {
        date: normalizedDate,
        userId: userId,
      },
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
      orderBy: { order: 'asc' },
    });

    return checkboxes.map(this.mapPrismaCheckbox);
  }

  /**
   * Ajoute une checkbox à une date donnée
   */
  async addCheckbox(data: CreateDailyCheckboxData): Promise<DailyCheckbox> {
    // Normaliser la date
    const normalizedDate = normalizeDate(data.date);

    // Calculer l'ordre suivant pour cette date
    const maxOrder = await prisma.dailyCheckbox.aggregate({
      where: { date: normalizedDate },
      _max: { order: true },
    });

    const order = data.order ?? (maxOrder._max.order ?? -1) + 1;

    const checkbox = await prisma.dailyCheckbox.create({
      data: {
        date: normalizedDate,
        text: data.text.trim(),
        type: data.type ?? 'task',
        taskId: data.taskId,
        userId: data.userId,
        order,
        isChecked: data.isChecked ?? false,
      },
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
    });

    return this.mapPrismaCheckbox(checkbox);
  }

  /**
   * Met à jour une checkbox
   */
  async updateCheckbox(
    checkboxId: string,
    data: UpdateDailyCheckboxData
  ): Promise<DailyCheckbox> {
    const updateData: Prisma.DailyCheckboxUpdateInput = {};

    if (data.text !== undefined) updateData.text = data.text.trim();
    if (data.isChecked !== undefined) updateData.isChecked = data.isChecked;
    if (data.type !== undefined) updateData.type = data.type;
    if (data.taskId !== undefined) {
      if (data.taskId === null) {
        updateData.task = { disconnect: true };
      } else {
        updateData.task = { connect: { id: data.taskId } };
      }
    }
    if (data.order !== undefined) updateData.order = data.order;
    if (data.date !== undefined) updateData.date = normalizeDate(data.date);

    const checkbox = await prisma.dailyCheckbox.update({
      where: { id: checkboxId },
      data: updateData,
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
    });

    return this.mapPrismaCheckbox(checkbox);
  }

  /**
   * Toggle l'état d'une checkbox par son ID (indépendant de la date)
   */
  async toggleCheckbox(checkboxId: string): Promise<DailyCheckbox> {
    const existing = await prisma.dailyCheckbox.findUnique({
      where: { id: checkboxId },
    });

    if (!existing) {
      throw new BusinessError('Checkbox non trouvée');
    }

    const updated = await prisma.dailyCheckbox.update({
      where: { id: checkboxId },
      data: { isChecked: !existing.isChecked, updatedAt: new Date() },
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
    });

    return this.mapPrismaCheckbox(updated);
  }

  /**
   * Supprime une checkbox
   */
  async deleteCheckbox(checkboxId: string): Promise<void> {
    const checkbox = await prisma.dailyCheckbox.findUnique({
      where: { id: checkboxId },
    });

    if (!checkbox) {
      throw new BusinessError('Checkbox non trouvée');
    }

    await prisma.dailyCheckbox.delete({
      where: { id: checkboxId },
    });
  }

  /**
   * Réordonne les checkboxes d'une date donnée
   */
  async reorderCheckboxes(date: Date, checkboxIds: string[]): Promise<void> {
    await prisma.$transaction(async (prisma) => {
      for (let i = 0; i < checkboxIds.length; i++) {
        await prisma.dailyCheckbox.update({
          where: { id: checkboxIds[i] },
          data: { order: i },
        });
      }
    });
  }

  /**
   * Recherche dans les checkboxes
   */
  async searchCheckboxes(
    query: string,
    limit: number = 20
  ): Promise<DailyCheckbox[]> {
    const checkboxes = await prisma.dailyCheckbox.findMany({
      where: {
        text: {
          contains: query,
        },
      },
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
      orderBy: { date: 'desc' },
      take: limit,
    });

    return checkboxes.map(this.mapPrismaCheckbox);
  }

  /**
   * Récupère l'historique des checkboxes (groupées par date)
   */
  async getCheckboxHistory(
    userId: string,
    limit: number = 30
  ): Promise<{ date: Date; checkboxes: DailyCheckbox[] }[]> {
    // Récupérer les dates distinctes des dernières checkboxes pour cet utilisateur
    const distinctDates = await prisma.dailyCheckbox.findMany({
      where: { userId },
      select: { date: true },
      distinct: ['date'],
      orderBy: { date: 'desc' },
      take: limit,
    });

    const history = [];
    for (const { date } of distinctDates) {
      const checkboxes = await this.getCheckboxesByDate(date, userId);
      if (checkboxes.length > 0) {
        history.push({ date, checkboxes });
      }
    }

    return history;
  }

  /**
   * Récupère la vue daily d'aujourd'hui
   */
  async getTodaysDailyView(userId: string): Promise<DailyView> {
    return this.getDailyView(getToday(), userId);
  }

  /**
   * Ajoute une checkbox pour aujourd'hui
   */
  async addTodayCheckbox(
    userId: string,
    text: string,
    taskId?: string
  ): Promise<DailyCheckbox> {
    return this.addCheckbox({
      date: getToday(),
      userId,
      text,
      taskId,
    });
  }

  /**
   * Ajoute une checkbox pour hier
   */
  async addYesterdayCheckbox(
    userId: string,
    text: string,
    taskId?: string
  ): Promise<DailyCheckbox> {
    return this.addCheckbox({
      date: getYesterday(),
      userId,
      text,
      taskId,
    });
  }

  /**
   * Mappe une checkbox Prisma vers notre interface
   * Accepte les checkboxes avec ou sans les relations taskTags et primaryTag
   */
  private mapPrismaCheckbox(
    checkbox: Prisma.DailyCheckboxGetPayload<{
      include: {
        task:
          | true
          | {
              include: {
                taskTags: {
                  include: {
                    tag: true;
                  };
                };
                primaryTag: true;
              };
            };
        user: true;
      };
    }>
  ): DailyCheckbox {
    // Extraire les tags de la tâche si elle existe
    let taskTags: string[] = [];
    let taskTagDetails:
      | Array<{ id: string; name: string; color: string; isPinned?: boolean }>
      | undefined = undefined;
    let taskPrimaryTag:
      | { id: string; name: string; color: string; isPinned?: boolean }
      | undefined = undefined;

    if (checkbox.task) {
      // Vérifier si taskTags est disponible (peut être true ou un objet avec include)
      const taskWithTags = checkbox.task as unknown as {
        taskTags?: Array<{
          tag: { id: string; name: string; color: string; isPinned: boolean };
        }>;
        primaryTag?: {
          id: string;
          name: string;
          color: string;
          isPinned: boolean;
        } | null;
      };

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

      // Extraire le primaryTag si disponible
      if ('primaryTag' in taskWithTags && taskWithTags.primaryTag) {
        taskPrimaryTag = {
          id: taskWithTags.primaryTag.id,
          name: taskWithTags.primaryTag.name,
          color: taskWithTags.primaryTag.color,
          isPinned: taskWithTags.primaryTag.isPinned,
        };
      }
    }

    return {
      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: taskTags,
            tagDetails: taskTagDetails,
            primaryTagId: checkbox.task.primaryTagId || undefined,
            primaryTag: taskPrimaryTag,
            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,
            assignee: checkbox.task.assignee || undefined,
            ownerId: (checkbox.task as unknown as { ownerId: string }).ownerId, // Cast temporaire jusqu'à ce que Prisma soit mis à jour
          }
        : undefined,
      isArchived: checkbox.text.includes('[ARCHIVÉ]'),
      createdAt: checkbox.createdAt,
      updatedAt: checkbox.updatedAt,
    };
  }

  /**
   * Récupère toutes les dates qui ont des checkboxes (pour le calendrier)
   */
  async getDailyDates(userId?: string): Promise<string[]> {
    const whereConditions: Prisma.DailyCheckboxWhereInput = {};

    // Filtrer par utilisateur si spécifié
    if (userId) {
      whereConditions.userId = userId;
    }

    const checkboxes = await prisma.dailyCheckbox.findMany({
      where: whereConditions,
      select: {
        date: true,
      },
      distinct: ['date'],
      orderBy: {
        date: 'desc',
      },
    });

    return checkboxes.map((checkbox) => {
      return formatDateForAPI(checkbox.date);
    });
  }

  /**
   * Récupère toutes les dates de fin (dueDate) des tâches non terminées (pour le calendrier)
   * Retourne un objet avec les dates comme clés et les tâches associées
   */
  async getTaskDeadlineDates(
    userId: string
  ): Promise<Record<string, string[]>> {
    const tasks = await prisma.task.findMany({
      where: {
        ownerId: userId,
        dueDate: {
          not: null,
        },
        status: {
          notIn: ['done', 'cancelled', 'archived'],
        },
      },
      select: {
        id: true,
        title: true,
        dueDate: true,
      },
      orderBy: {
        dueDate: 'asc',
      },
    });

    const datesMap: Record<string, string[]> = {};

    tasks.forEach((task) => {
      if (!task.dueDate) return;
      // Normaliser la date pour éviter les problèmes de timezone
      const normalizedDate = normalizeDate(task.dueDate);
      const dateKey = formatDateForAPI(normalizedDate);

      if (!datesMap[dateKey]) {
        datesMap[dateKey] = [];
      }
      datesMap[dateKey].push(task.title);
    });

    return datesMap;
  }

  /**
   * Récupère les tâches avec deadline pour une date donnée
   * Retourne un tableau de tâches complètes
   */
  async getTasksByDeadlineDate(userId: string, date: Date): Promise<Task[]> {
    const normalizedDate = normalizeDate(date);
    const dateKey = formatDateForAPI(normalizedDate);

    const tasks = await prisma.task.findMany({
      where: {
        ownerId: userId,
        dueDate: {
          not: null,
        },
        status: {
          notIn: ['done', 'cancelled', 'archived'],
        },
      },
      include: {
        taskTags: {
          include: {
            tag: true,
          },
        },
        primaryTag: true,
        _count: {
          select: {
            dailyCheckboxes: true,
          },
        },
      },
      orderBy: {
        dueDate: 'asc',
      },
    });

    // Filtrer les tâches dont la date de fin correspond à la date demandée
    const filteredTasks = tasks
      .filter((task) => {
        if (!task.dueDate) return false;
        const taskDateKey = formatDateForAPI(normalizeDate(task.dueDate));
        return taskDateKey === dateKey;
      })
      .map((task) => ({
        id: task.id,
        title: task.title,
        description: task.description ?? undefined,
        status: task.status as TaskStatus,
        priority: task.priority as TaskPriority,
        source: task.source as TaskSource,
        sourceId: task.sourceId ?? undefined,
        tags: task.taskTags.map((tt) => tt.tag.name),
        tagDetails: task.taskTags.map((tt) => ({
          id: tt.tag.id,
          name: tt.tag.name,
          color: tt.tag.color,
          isPinned: tt.tag.isPinned,
        })),
        primaryTagId: task.primaryTagId ?? undefined,
        primaryTag: task.primaryTag
          ? {
              id: task.primaryTag.id,
              name: task.primaryTag.name,
              color: task.primaryTag.color,
              isPinned: task.primaryTag.isPinned,
            }
          : undefined,
        dueDate: task.dueDate ?? undefined,
        completedAt: task.completedAt ?? undefined,
        createdAt: task.createdAt,
        updatedAt: task.updatedAt,
        jiraProject: task.jiraProject ?? undefined,
        jiraKey: task.jiraKey ?? undefined,
        jiraType: task.jiraType ?? undefined,
        tfsProject: task.tfsProject ?? undefined,
        tfsPullRequestId: task.tfsPullRequestId ?? undefined,
        tfsRepository: task.tfsRepository ?? undefined,
        tfsSourceBranch: task.tfsSourceBranch ?? undefined,
        tfsTargetBranch: task.tfsTargetBranch ?? undefined,
        assignee: task.assignee ?? undefined,
        ownerId: (task as unknown as { ownerId: string }).ownerId,
        todosCount: task._count.dailyCheckboxes,
      }));

    return filteredTasks;
  }

  /**
   * Récupère toutes les checkboxes non cochées (tâches en attente)
   */
  async getPendingCheckboxes(options?: {
    maxDays?: number;
    excludeToday?: boolean;
    type?: DailyCheckboxType;
    limit?: number;
    userId?: string; // Filtrer par utilisateur
  }): Promise<DailyCheckbox[]> {
    const today = normalizeDate(getToday());
    const maxDays = options?.maxDays ?? 30;
    const excludeToday = options?.excludeToday ?? true;

    // Calculer la date limite (maxDays jours en arrière)
    const limitDate = new Date(today);
    limitDate.setDate(limitDate.getDate() - maxDays);

    // Construire les conditions de filtrage
    const whereConditions: Prisma.DailyCheckboxWhereInput = {
      isChecked: false,
      date: {
        gte: limitDate,
        ...(excludeToday ? { lt: today } : { lte: today }),
      },
    };

    // Filtrer par type si spécifié
    if (options?.type) {
      whereConditions.type = options.type;
    }

    // Filtrer par utilisateur si spécifié
    if (options?.userId) {
      whereConditions.userId = options.userId;

      // S'assurer que si le todo est lié à une tâche, cette tâche appartient bien à l'utilisateur
      whereConditions.OR = [
        { task: null }, // Todos standalone (sans tâche associée)
        { task: { ownerId: options.userId } }, // Todos liés à une tâche de l'utilisateur
      ];
    }

    const checkboxes = await prisma.dailyCheckbox.findMany({
      where: whereConditions,
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
      orderBy: [{ date: 'desc' }, { order: 'asc' }],
      ...(options?.limit ? { take: options.limit } : {}),
    });

    return checkboxes.map(this.mapPrismaCheckbox);
  }

  /**
   * Archive une checkbox (marque comme archivée sans la cocher)
   */
  async archiveCheckbox(checkboxId: string): Promise<DailyCheckbox> {
    // Pour l'instant, on utilise un champ text pour marquer comme archivé
    // Plus tard on pourra ajouter un champ dédié dans la DB
    const checkbox = await prisma.dailyCheckbox.update({
      where: { id: checkboxId },
      data: {
        text:
          (await prisma.dailyCheckbox.findUnique({ where: { id: checkboxId } }))
            ?.text + ' [ARCHIVÉ]',
        updatedAt: new Date(),
      },
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
    });

    return this.mapPrismaCheckbox(checkbox);
  }

  /**
   * Déplace une checkbox non cochée à aujourd'hui
   */
  async moveCheckboxToToday(checkboxId: string): Promise<DailyCheckbox> {
    const checkbox = await prisma.dailyCheckbox.findUnique({
      where: { id: checkboxId },
    });

    if (!checkbox) {
      throw new BusinessError('Checkbox non trouvée');
    }

    if (checkbox.isChecked) {
      throw new BusinessError('Impossible de déplacer une tâche déjà cochée');
    }

    const today = normalizeDate(getToday());

    // Vérifier si la checkbox est déjà pour aujourd'hui
    if (normalizeDate(checkbox.date).getTime() === today.getTime()) {
      throw new BusinessError("La tâche est déjà programmée pour aujourd'hui");
    }

    // Calculer l'ordre suivant pour aujourd'hui
    const maxOrder = await prisma.dailyCheckbox.aggregate({
      where: { date: today },
      _max: { order: true },
    });

    const newOrder = (maxOrder._max.order ?? -1) + 1;

    const updatedCheckbox = await prisma.dailyCheckbox.update({
      where: { id: checkboxId },
      data: {
        date: today,
        order: newOrder,
        updatedAt: new Date(),
      },
      include: {
        task: {
          include: {
            taskTags: {
              include: {
                tag: true,
              },
            },
            primaryTag: true,
          },
        },
        user: true,
      },
    });

    return this.mapPrismaCheckbox(updatedCheckbox);
  }
}

// Instance singleton du service
export const dailyService = new DailyService();
