import * as fs from "fs";
import * as path from "path";
import { v4 as uuidv4 } from "uuid";
import { logger } from "../utils/logger";
import { query } from "../database/db";

const TODO_DIR = path.join(__dirname, "../../data/todos");
const TODO_FILE = path.join(TODO_DIR, "todos.json");

// 환경 변수로 데이터 소스 선택 (file | database)
const DATA_SOURCE = process.env.TODO_DATA_SOURCE || "file";

export interface TodoItem {
  id: string;
  title: string;
  description?: string;
  priority: "urgent" | "high" | "normal" | "low";
  status: "pending" | "in_progress" | "completed";
  assignedTo?: string;
  dueDate?: string;
  createdAt: string;
  updatedAt: string;
  completedAt?: string;
  isUrgent: boolean;
  order: number;
}

export interface TodoListResponse {
  todos: TodoItem[];
  stats: {
    total: number;
    pending: number;
    inProgress: number;
    completed: number;
    urgent: number;
    overdue: number;
  };
}

/**
 * To-Do 리스트 관리 서비스 (File/DB 하이브리드)
 */
export class TodoService {
  private static instance: TodoService;

  private constructor() {
    if (DATA_SOURCE === "file") {
      this.ensureDataDirectory();
    }
    logger.info(`📋 To-Do 데이터 소스: ${DATA_SOURCE.toUpperCase()}`);
  }

  public static getInstance(): TodoService {
    if (!TodoService.instance) {
      TodoService.instance = new TodoService();
    }
    return TodoService.instance;
  }

  /**
   * 데이터 디렉토리 생성 (파일 모드)
   */
  private ensureDataDirectory(): void {
    try {
      if (!fs.existsSync(TODO_DIR)) {
        fs.mkdirSync(TODO_DIR, { recursive: true, mode: 0o755 });
        logger.info(`📁 To-Do 데이터 디렉토리 생성: ${TODO_DIR}`);
      }
      if (!fs.existsSync(TODO_FILE)) {
        fs.writeFileSync(TODO_FILE, JSON.stringify([], null, 2), {
          mode: 0o644,
        });
        logger.info(`📄 To-Do 파일 생성: ${TODO_FILE}`);
      }
    } catch (error) {
      logger.error(`❌ To-Do 디렉토리 생성 실패: ${TODO_DIR}`, error);
      throw error;
    }
  }

  /**
   * 모든 To-Do 항목 조회
   */
  public async getAllTodos(filter?: {
    status?: string;
    priority?: string;
    assignedTo?: string;
  }): Promise<TodoListResponse> {
    try {
      const todos =
        DATA_SOURCE === "database"
          ? await this.loadTodosFromDB(filter)
          : this.loadTodosFromFile(filter);

      // 정렬: 긴급 > 우선순위 > 순서
      todos.sort((a, b) => {
        if (a.isUrgent !== b.isUrgent) return a.isUrgent ? -1 : 1;
        const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };
        if (a.priority !== b.priority)
          return priorityOrder[a.priority] - priorityOrder[b.priority];
        return a.order - b.order;
      });

      const stats = this.calculateStats(todos);

      return { todos, stats };
    } catch (error) {
      logger.error("❌ To-Do 목록 조회 오류:", error);
      throw error;
    }
  }

  /**
   * To-Do 항목 생성
   */
  public async createTodo(todoData: Partial<TodoItem>): Promise<TodoItem> {
    try {
      const newTodo: TodoItem = {
        id: uuidv4(),
        title: todoData.title || "",
        description: todoData.description,
        priority: todoData.priority || "normal",
        status: "pending",
        assignedTo: todoData.assignedTo,
        dueDate: todoData.dueDate,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        isUrgent: todoData.isUrgent || false,
        order: 0, // DB에서 자동 계산
      };

      if (DATA_SOURCE === "database") {
        await this.createTodoDB(newTodo);
      } else {
        const todos = this.loadTodosFromFile();
        newTodo.order =
          todos.length > 0 ? Math.max(...todos.map((t) => t.order)) + 1 : 0;
        todos.push(newTodo);
        this.saveTodosToFile(todos);
      }

      logger.info(`✅ To-Do 생성: ${newTodo.id} - ${newTodo.title}`);
      return newTodo;
    } catch (error) {
      logger.error("❌ To-Do 생성 오류:", error);
      throw error;
    }
  }

  /**
   * To-Do 항목 수정
   */
  public async updateTodo(
    id: string,
    updates: Partial<TodoItem>
  ): Promise<TodoItem> {
    try {
      // 먼저 데이터베이스에서 찾아보고, 없으면 파일에서 찾기
      try {
        return await this.updateTodoDB(id, updates);
      } catch (dbError: any) {
        // 데이터베이스에서 찾지 못했으면 파일에서 찾기
        if (dbError.message && dbError.message.includes("찾을 수 없습니다")) {
          return this.updateTodoFile(id, updates);
        }
        throw dbError;
      }
    } catch (error) {
      logger.error("❌ To-Do 수정 오류:", error);
      throw error;
    }
  }

  /**
   * To-Do 항목 삭제
   */
  public async deleteTodo(id: string): Promise<void> {
    try {
      // 먼저 데이터베이스에서 찾아보고, 없으면 파일에서 찾기
      try {
        await this.deleteTodoDB(id);
      } catch (dbError: any) {
        // 데이터베이스에서 찾지 못했으면 파일에서 찾기
        if (dbError.message && dbError.message.includes("찾을 수 없습니다")) {
          this.deleteTodoFile(id);
        } else {
          throw dbError;
        }
      }
      logger.info(`✅ To-Do 삭제: ${id}`);
    } catch (error) {
      logger.error("❌ To-Do 삭제 오류:", error);
      throw error;
    }
  }

  /**
   * To-Do 항목 순서 변경
   */
  public async reorderTodos(todoIds: string[]): Promise<void> {
    try {
      if (DATA_SOURCE === "database") {
        await this.reorderTodosDB(todoIds);
      } else {
        this.reorderTodosFile(todoIds);
      }
      logger.info(`✅ To-Do 순서 변경: ${todoIds.length}개 항목`);
    } catch (error) {
      logger.error("❌ To-Do 순서 변경 오류:", error);
      throw error;
    }
  }

  // ==================== DATABASE 메서드 ====================

  private async loadTodosFromDB(filter?: {
    status?: string;
    priority?: string;
    assignedTo?: string;
  }): Promise<TodoItem[]> {
    let sql = `
      SELECT 
        id, title, description, priority, status, 
        assigned_to as "assignedTo", 
        due_date as "dueDate",
        created_at as "createdAt", 
        updated_at as "updatedAt",
        completed_at as "completedAt",
        is_urgent as "isUrgent",
        display_order as "order"
      FROM todo_items
      WHERE 1=1
    `;
    const params: any[] = [];
    let paramIndex = 1;

    if (filter?.status) {
      sql += ` AND status = $${paramIndex++}`;
      params.push(filter.status);
    }
    if (filter?.priority) {
      sql += ` AND priority = $${paramIndex++}`;
      params.push(filter.priority);
    }
    if (filter?.assignedTo) {
      sql += ` AND assigned_to = $${paramIndex++}`;
      params.push(filter.assignedTo);
    }

    sql += ` ORDER BY display_order ASC`;

    const rows = await query(sql, params);
    return rows.map((row: any) => ({
      ...row,
      dueDate: row.dueDate ? new Date(row.dueDate).toISOString() : undefined,
      createdAt: new Date(row.createdAt).toISOString(),
      updatedAt: new Date(row.updatedAt).toISOString(),
      completedAt: row.completedAt
        ? new Date(row.completedAt).toISOString()
        : undefined,
    }));
  }

  private async createTodoDB(todo: TodoItem): Promise<void> {
    // 현재 최대 order 값 조회
    const maxOrderRows = await query(
      "SELECT COALESCE(MAX(display_order), -1) + 1 as next_order FROM todo_items"
    );
    const nextOrder = maxOrderRows[0].next_order;

    await query(
      `INSERT INTO todo_items (
        id, title, description, priority, status, assigned_to, due_date,
        created_at, updated_at, is_urgent, display_order
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
      [
        todo.id,
        todo.title,
        todo.description,
        todo.priority,
        todo.status,
        todo.assignedTo,
        todo.dueDate ? new Date(todo.dueDate) : null,
        new Date(todo.createdAt),
        new Date(todo.updatedAt),
        todo.isUrgent,
        nextOrder,
      ]
    );
  }

  private async updateTodoDB(
    id: string,
    updates: Partial<TodoItem>
  ): Promise<TodoItem> {
    const setClauses: string[] = ["updated_at = NOW()"];
    const params: any[] = [];
    let paramIndex = 1;

    if (updates.title !== undefined) {
      setClauses.push(`title = $${paramIndex++}`);
      params.push(updates.title);
    }
    if (updates.description !== undefined) {
      setClauses.push(`description = $${paramIndex++}`);
      params.push(updates.description);
    }
    if (updates.priority !== undefined) {
      setClauses.push(`priority = $${paramIndex++}`);
      params.push(updates.priority);
    }
    if (updates.status !== undefined) {
      setClauses.push(`status = $${paramIndex++}`);
      params.push(updates.status);
      if (updates.status === "completed") {
        setClauses.push(`completed_at = NOW()`);
      }
    }
    if (updates.assignedTo !== undefined) {
      setClauses.push(`assigned_to = $${paramIndex++}`);
      params.push(updates.assignedTo);
    }
    if (updates.dueDate !== undefined) {
      setClauses.push(`due_date = $${paramIndex++}`);
      params.push(updates.dueDate ? new Date(updates.dueDate) : null);
    }
    if (updates.isUrgent !== undefined) {
      setClauses.push(`is_urgent = $${paramIndex++}`);
      params.push(updates.isUrgent);
    }

    params.push(id);
    const sql = `
      UPDATE todo_items 
      SET ${setClauses.join(", ")}
      WHERE id = $${paramIndex}
      RETURNING 
        id, title, description, priority, status,
        assigned_to as "assignedTo",
        due_date as "dueDate",
        created_at as "createdAt",
        updated_at as "updatedAt",
        completed_at as "completedAt",
        is_urgent as "isUrgent",
        display_order as "order"
    `;

    const rows = await query(sql, params);
    if (rows.length === 0) {
      throw new Error(`To-Do 항목을 찾을 수 없습니다: ${id}`);
    }

    const row = rows[0];
    return {
      ...row,
      dueDate: row.dueDate ? new Date(row.dueDate).toISOString() : undefined,
      createdAt: new Date(row.createdAt).toISOString(),
      updatedAt: new Date(row.updatedAt).toISOString(),
      completedAt: row.completedAt
        ? new Date(row.completedAt).toISOString()
        : undefined,
    };
  }

  private async deleteTodoDB(id: string): Promise<void> {
    const rows = await query(
      "DELETE FROM todo_items WHERE id = $1 RETURNING id",
      [id]
    );
    if (rows.length === 0) {
      throw new Error(`To-Do 항목을 찾을 수 없습니다: ${id}`);
    }
  }

  private async reorderTodosDB(todoIds: string[]): Promise<void> {
    for (let i = 0; i < todoIds.length; i++) {
      await query(
        "UPDATE todo_items SET display_order = $1, updated_at = NOW() WHERE id = $2",
        [i, todoIds[i]]
      );
    }
  }

  // ==================== FILE 메서드 ====================

  private loadTodosFromFile(filter?: {
    status?: string;
    priority?: string;
    assignedTo?: string;
  }): TodoItem[] {
    try {
      const data = fs.readFileSync(TODO_FILE, "utf-8");
      let todos: TodoItem[] = JSON.parse(data);

      if (filter?.status) {
        todos = todos.filter((t) => t.status === filter.status);
      }
      if (filter?.priority) {
        todos = todos.filter((t) => t.priority === filter.priority);
      }
      if (filter?.assignedTo) {
        todos = todos.filter((t) => t.assignedTo === filter.assignedTo);
      }

      return todos;
    } catch (error) {
      logger.error("❌ To-Do 파일 로드 오류:", error);
      return [];
    }
  }

  private saveTodosToFile(todos: TodoItem[]): void {
    try {
      fs.writeFileSync(TODO_FILE, JSON.stringify(todos, null, 2));
    } catch (error) {
      logger.error("❌ To-Do 파일 저장 오류:", error);
      throw error;
    }
  }

  private updateTodoFile(id: string, updates: Partial<TodoItem>): TodoItem {
    const todos = this.loadTodosFromFile();
    const index = todos.findIndex((t) => t.id === id);

    if (index === -1) {
      throw new Error(`To-Do 항목을 찾을 수 없습니다: ${id}`);
    }

    const updatedTodo: TodoItem = {
      ...todos[index],
      ...updates,
      updatedAt: new Date().toISOString(),
    };

    if (updates.status === "completed" && todos[index].status !== "completed") {
      updatedTodo.completedAt = new Date().toISOString();
    }

    todos[index] = updatedTodo;
    this.saveTodosToFile(todos);

    return updatedTodo;
  }

  private deleteTodoFile(id: string): void {
    const todos = this.loadTodosFromFile();
    const filteredTodos = todos.filter((t) => t.id !== id);

    if (todos.length === filteredTodos.length) {
      throw new Error(`To-Do 항목을 찾을 수 없습니다: ${id}`);
    }

    this.saveTodosToFile(filteredTodos);
  }

  private reorderTodosFile(todoIds: string[]): void {
    const todos = this.loadTodosFromFile();

    todoIds.forEach((id, index) => {
      const todo = todos.find((t) => t.id === id);
      if (todo) {
        todo.order = index;
        todo.updatedAt = new Date().toISOString();
      }
    });

    this.saveTodosToFile(todos);
  }

  // ==================== 공통 메서드 ====================

  private calculateStats(todos: TodoItem[]): TodoListResponse["stats"] {
    const now = new Date();
    return {
      total: todos.length,
      pending: todos.filter((t) => t.status === "pending").length,
      inProgress: todos.filter((t) => t.status === "in_progress").length,
      completed: todos.filter((t) => t.status === "completed").length,
      urgent: todos.filter((t) => t.isUrgent).length,
      overdue: todos.filter(
        (t) =>
          t.dueDate && new Date(t.dueDate) < now && t.status !== "completed"
      ).length,
    };
  }
}
