import {
  Injectable,
  Logger,
  NotFoundException,
  type OnModuleInit,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { LessThanOrEqual, MoreThanOrEqual, Repository } from 'typeorm';
import { User } from '../user/entities/user.entity';
import { UserService } from '../user/user.service';
import { ScheduleCategory } from './entities/schedule-category.entity';
import { ScheduleEvent } from './entities/schedule-event.entity';
import { CreateScheduleCategoryDto } from './dto/schedule-category.dto';
import { CreateScheduleEventDto } from './dto/schedule-event.dto';

// 카테고리/일정 응답(원시) — 표시 음영색은 프론트가 color 로 파생
export interface ScheduleCategoryResponse {
  id: string;
  label: string;
  color: string;
  sortOrder: number;
}

export interface ScheduleAttendee {
  id: string;
  name: string;
  avatarUrl: string | null;
}

export interface ScheduleEventResponse {
  id: string;
  categoryId: string;
  title: string;
  start: string; // YYYY-MM-DD
  end: string; // YYYY-MM-DD
  time: string;
  place: string | null;
  description: string | null;
  allHands: boolean;
  attendees: ScheduleAttendee[];
  createdById: string | null;
}

// 기본 카테고리 시드 — 최초 1회만 적재(테이블 비어있을 때). label/color/순서.
const DEFAULT_CATEGORIES: { label: string; color: string }[] = [
  { label: '회의', color: '#1d4ed8' },
  { label: '외부 미팅', color: '#0284c7' },
  { label: '채용·면접', color: '#be185d' },
  { label: '사내 행사', color: '#4f46e5' },
  { label: '교육·세미나', color: '#1f9d57' },
  { label: '휴가·부재', color: '#b7791f' },
  { label: '회식·모임', color: '#9333ea' },
  { label: '마감·릴리스', color: '#dc2626' },
  { label: '리뷰·QA', color: '#0d9488' },
  { label: '재무·결산', color: '#475569' },
  { label: '홍보·PR', color: '#ea580c' },
  { label: '기타', color: '#0e9594' },
];

// 일정(전사 공유 캘린더) 비즈니스 로직 — 카테고리/일정 CRUD + 참석자 매핑.
@Injectable()
export class ScheduleService implements OnModuleInit {
  private readonly logger = new Logger(ScheduleService.name);

  constructor(
    @InjectRepository(ScheduleCategory)
    private readonly categoryRepo: Repository<ScheduleCategory>,
    @InjectRepository(ScheduleEvent)
    private readonly eventRepo: Repository<ScheduleEvent>,
    private readonly userService: UserService,
  ) {}

  // 기동 시 기본 카테고리 시드(멱등) — 비어있을 때만 적재. dev/prod 공통.
  async onModuleInit(): Promise<void> {
    const count = await this.categoryRepo.count();
    if (count > 0) return;
    const rows = DEFAULT_CATEGORIES.map((c, i) =>
      this.categoryRepo.create({
        label: c.label,
        color: c.color,
        sortOrder: i,
      }),
    );
    await this.categoryRepo.save(rows);
    this.logger.log(`기본 일정 카테고리 ${rows.length}건 시드`);
  }

  // ----- 카테고리 -----

  async listCategories(): Promise<ScheduleCategoryResponse[]> {
    const rows = await this.categoryRepo.find({
      order: { sortOrder: 'ASC', createdAt: 'ASC' },
    });
    return rows.map((c) => this.toCategoryResponse(c));
  }

  async createCategory(
    dto: CreateScheduleCategoryDto,
  ): Promise<ScheduleCategoryResponse> {
    // 다음 정렬값 = 현재 최대 + 1
    const max = await this.categoryRepo
      .createQueryBuilder('c')
      .select('MAX(c.sort_order)', 'max')
      .getRawOne<{ max: number | null }>();
    const sortOrder = (max?.max ?? -1) + 1;
    const saved = await this.categoryRepo.save(
      this.categoryRepo.create({
        label: dto.label.trim(),
        color: dto.color,
        sortOrder,
      }),
    );
    return this.toCategoryResponse(saved);
  }

  async updateCategory(
    id: string,
    dto: Partial<CreateScheduleCategoryDto>,
  ): Promise<ScheduleCategoryResponse> {
    const cat = await this.categoryRepo.findOne({ where: { id } });
    if (!cat) throw new NotFoundException('카테고리를 찾을 수 없습니다.');
    if (dto.label !== undefined) cat.label = dto.label.trim();
    if (dto.color !== undefined) cat.color = dto.color;
    const saved = await this.categoryRepo.save(cat);
    return this.toCategoryResponse(saved);
  }

  // 카테고리 삭제 — 포함된 일정도 FK CASCADE 로 함께 삭제된다.
  async deleteCategory(id: string): Promise<void> {
    const res = await this.categoryRepo.delete({ id });
    if (!res.affected)
      throw new NotFoundException('카테고리를 찾을 수 없습니다.');
  }

  // ----- 일정 -----

  // 주간(또는 임의 기간) 조회 — [start, end] 와 겹치는 일정 전부.
  async listEvents(
    start: string,
    end: string,
  ): Promise<ScheduleEventResponse[]> {
    const rows = await this.eventRepo.find({
      where: {
        startDate: LessThanOrEqual(end),
        endDate: MoreThanOrEqual(start),
      },
      relations: ['attendees', 'createdBy'],
      order: { startDate: 'ASC', time: 'ASC' },
    });
    return rows.map((e) => this.toEventResponse(e));
  }

  async createEvent(
    dto: CreateScheduleEventDto,
    actorId: string,
  ): Promise<ScheduleEventResponse> {
    const category = await this.categoryRepo.findOne({
      where: { id: dto.categoryId },
    });
    if (!category) throw new NotFoundException('카테고리를 찾을 수 없습니다.');

    const event = this.eventRepo.create({
      category: { id: category.id } as ScheduleCategory,
      title: dto.title.trim(),
      startDate: dto.start,
      endDate: dto.end,
      time: dto.time?.trim() ?? '',
      place: dto.place?.trim() || null,
      description: dto.description?.trim() || null,
      allHands: dto.allHands ?? false,
      attendees: this.attendeeRefs(dto.allHands, dto.attendeeIds),
      createdBy: { id: actorId } as User,
    });
    const saved = await this.eventRepo.save(event);
    return this.findEventOrThrow(saved.id);
  }

  async updateEvent(
    id: string,
    dto: Partial<CreateScheduleEventDto>,
  ): Promise<ScheduleEventResponse> {
    const event = await this.eventRepo.findOne({
      where: { id },
      relations: ['attendees'],
    });
    if (!event) throw new NotFoundException('일정을 찾을 수 없습니다.');

    if (dto.categoryId !== undefined) {
      const category = await this.categoryRepo.findOne({
        where: { id: dto.categoryId },
      });
      if (!category)
        throw new NotFoundException('카테고리를 찾을 수 없습니다.');
      event.category = { id: category.id } as ScheduleCategory;
    }
    if (dto.title !== undefined) event.title = dto.title.trim();
    if (dto.start !== undefined) event.startDate = dto.start;
    if (dto.end !== undefined) event.endDate = dto.end;
    if (dto.time !== undefined) event.time = dto.time.trim();
    if (dto.place !== undefined) event.place = dto.place.trim() || null;
    if (dto.description !== undefined)
      event.description = dto.description.trim() || null;
    if (dto.allHands !== undefined) event.allHands = dto.allHands;
    // 참석자/전직원 변경이 있으면 재매핑
    if (dto.allHands !== undefined || dto.attendeeIds !== undefined) {
      const allHands = dto.allHands ?? event.allHands;
      event.attendees = this.attendeeRefs(allHands, dto.attendeeIds);
    }
    await this.eventRepo.save(event);
    return this.findEventOrThrow(id);
  }

  async deleteEvent(id: string): Promise<void> {
    const res = await this.eventRepo.delete({ id });
    if (!res.affected) throw new NotFoundException('일정을 찾을 수 없습니다.');
  }

  // 참석자 선택 풀(전사 사용자) — 일정 작성 모달의 참석자 선택용.
  async listPeople(): Promise<ScheduleAttendee[]> {
    const users = await this.userService.search('', 500);
    return users.map((u) => ({
      id: u.id,
      name: u.name,
      avatarUrl: u.avatarUrl ?? null,
    }));
  }

  // ----- 내부 헬퍼 -----

  // 전 직원 일정이면 개별 참석자는 비운다. 아니면 id 참조 배열로 매핑.
  private attendeeRefs(allHands?: boolean, ids?: string[]): User[] {
    if (allHands) return [];
    return [...new Set(ids ?? [])].map((id) => ({ id }) as User);
  }

  private async findEventOrThrow(id: string): Promise<ScheduleEventResponse> {
    const e = await this.eventRepo.findOne({
      where: { id },
      relations: ['attendees', 'createdBy'],
    });
    if (!e) throw new NotFoundException('일정을 찾을 수 없습니다.');
    return this.toEventResponse(e);
  }

  private toCategoryResponse(c: ScheduleCategory): ScheduleCategoryResponse {
    return {
      id: c.id,
      label: c.label,
      color: c.color,
      sortOrder: c.sortOrder,
    };
  }

  private toEventResponse(e: ScheduleEvent): ScheduleEventResponse {
    return {
      id: e.id,
      categoryId: e.category?.id ?? '',
      title: e.title,
      start: e.startDate,
      end: e.endDate,
      time: e.time ?? '',
      place: e.place ?? null,
      description: e.description ?? null,
      allHands: e.allHands,
      attendees: (e.attendees ?? []).map((u) => ({
        id: u.id,
        name: u.name,
        avatarUrl: u.avatarUrl ?? null,
      })),
      createdById: e.createdBy?.id ?? null,
    };
  }
}
