import 'package:flutter/foundation.dart';
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../config/api_config.dart';
import '../models/event_model.dart';
import '../models/participant_model.dart';
import '../models/score_model.dart';
import '../models/team_model.dart';
import './api_service.dart';

class EventService with ChangeNotifier {
  List<Event> _events = [];
  Event? _currentEvent;
  List<Participant> _participants = [];
  List<Score> _scores = [];
  List<Team> _teams = [];
  bool _isLoading = false;
  String? _token;
  final ApiService _apiService;
  String? _clubId;

  // 기본 생성자
  EventService() : _apiService = ApiService();

  // 테스트용 생성자
  EventService.forTest(this._apiService);

  List<Event> get events => [..._events];
  Event? get currentEvent => _currentEvent;
  List<Participant> get participants => [..._participants];
  List<Score> get scores => [..._scores];
  List<Team> get teams => [..._teams];
  bool get isLoading => _isLoading;

  // 초기화 함수
  Future<void> initialize(String token) async {
    _token = token;
    _apiService.setToken(token);
  }

  // [팀 생성 기능] 서버 포맷으로 팀 배정 저장
  // serverTeams 형태 예시:
  // [
  //   { 'teamNumber': 1, 'handicap': 0, 'members': [ { 'id': <eventParticipantId>, 'order': 1 }, ... ] },
  //   ...
  // ]
  Future<bool> createOrUpdateEventTeams(String eventId, List<Map<String, dynamic>> serverTeams) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      await _apiService.post(
        '${ApiConfig.clubs}/events/$eventId/teams',
        data: {
          'clubId': _clubId,
          'teams': serverTeams,
        },
      );
      _isLoading = false;
      notifyListeners();
      return true;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('팀 배정 저장에 실패했습니다: $e');
    }
  }

  // 클럽 ID 설정
  void setClubId(String clubId) {
    _clubId = clubId;
    notifyListeners();
  }

  // 클럽의 모든 이벤트 가져오기
  Future<void> fetchClubEvents() async {
    print('fetchClubEvents 호출됨: token=$_token');

    _isLoading = true;
    notifyListeners();

    try {
      // 클럽 ID 가져오기
      final prefs = await SharedPreferences.getInstance();
      final clubId = prefs.getString(ApiConfig.clubIdKey);

      if (clubId == null) {
        throw Exception('선택된 클럽이 없습니다');
      }

      final data = await _apiService.post(
        '${ApiConfig.clubs}/events',
        data: {'clubId': clubId},
      );

      // 응답 데이터가 리스트 형태로 직접 왔는지 확인
      if (data is List) {
        final List<dynamic> eventsData = data;
        _events = eventsData
            .map((eventData) => Event.fromJson(eventData))
            .toList();
      } else {
        // 응답 데이터가 객체 형태로 왔을 경우 events 키를 통해 접근
        final List<dynamic> eventsData = data['events'] ?? [];
        _events = eventsData
            .map((eventData) => Event.fromJson(eventData))
            .toList();
      }

      _isLoading = false;
      notifyListeners();
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('이벤트 목록을 불러오는데 실패했습니다: $e');
    }
  }

  // ID로 이벤트 정보 가져오기
  Future<Event> fetchEventById(String eventId) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    try {
      // 백엔드 라우트: GET /api/club/events/:id
      final data = await _apiService.get(
        '${ApiConfig.clubs}/events/$eventId',
        queryParameters: {'clubId': _clubId},
      );

      // 서버가 { event: {...} } 또는 이벤트 객체 자체를 반환할 수 있음
      final dynamic body = data;
      final Map<String, dynamic> json =
          body is Map<String, dynamic>
              ? (body.containsKey('event') ? Map<String, dynamic>.from(body['event']) : body)
              : <String, dynamic>{};
      return Event.fromJson(json);
    } catch (e) {
      throw Exception('이벤트 정보를 불러오는데 실패했습니다: $e');
    }
  }

  // 이벤트 생성
  Future<Event> createEvent(Map<String, dynamic> eventData) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      final data = await _apiService.post(
        '${ApiConfig.clubs}/events',
        data: eventData,
      );

      final newEvent = Event.fromJson(data['event']);

      _events.add(newEvent);

      _isLoading = false;
      notifyListeners();

      return newEvent;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('이벤트 생성에 실패했습니다: $e');
    }
  }

  // 이벤트 정보 업데이트
  Future<Event> updateEvent(
    String eventId,
    Map<String, dynamic> updates,
  ) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }

    // 업데이트 전에 clubId가 비어 있으면 SharedPreferences에서 보강 시도
    if (_clubId == null) {
      try {
        final prefs = await SharedPreferences.getInstance();
        final cid = prefs.getString(ApiConfig.clubIdKey);
        if (cid != null) {
          _clubId = cid;
        }
      } catch (_) {}
    }
    if (_clubId == null) {
      throw Exception('클럽 정보가 아직 설정되지 않았습니다. 클럽 선택 후 다시 시도해 주세요.');
    }

    _isLoading = true;
    notifyListeners();

    try {
      // 빈 문자열을 명시적 null로 변환하여 서버에서 정확히 해석되도록 함
      final sanitized = Map<String, dynamic>.from(updates)..updateAll((key, value) {
        if (value is String && value.trim().isEmpty) return null;
        return value;
      });

      final data = await _apiService.put(
        '${ApiConfig.clubs}/events/$eventId',
        data: {
          'id': eventId,
          'clubId': _clubId,
          ...sanitized,
        },
      );

      final updatedEvent = Event.fromJson(data['event']);

      // 이벤트 목록 업데이트
      final index = _events.indexWhere((event) => event.id == eventId);
      if (index >= 0) {
        _events[index] = updatedEvent;
      }

      _isLoading = false;
      notifyListeners();

      return updatedEvent;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('이벤트 정보 업데이트에 실패했습니다: $e');
    }
  }

  // 이벤트 삭제
  Future<bool> deleteEvent(String eventId) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      await _apiService.deleteWithData(
        '${ApiConfig.clubs}/events/$eventId',
        data: { 'clubId': _clubId },
      );

      // 이벤트 목록에서 삭제
      _events.removeWhere((event) => event.id == eventId);

      _isLoading = false;
      notifyListeners();

      return true;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('이벤트 삭제에 실패했습니다: $e');
    }
  }

  // 이벤트 완전 삭제 (하드 삭제)
  Future<bool> deleteEventPermanently(String eventId) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      await _apiService.deleteWithData(
        '${ApiConfig.clubs}/events/$eventId/permanent',
        data: { 'clubId': _clubId },
      );

      // 이벤트 목록에서 제거
      _events.removeWhere((event) => event.id == eventId);

      _isLoading = false;
      notifyListeners();
      return true;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('이벤트 완전 삭제에 실패했습니다: $e');
    }
  }

  // 임포트 저장 롤백
  Future<bool> rollbackEventImport({
    required String eventId,
    String? clubId,
    bool includeGuests = false,
  }) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }
    // clubId 확보
    String? cid = clubId ?? _clubId;
    if (cid == null) {
      try {
        final prefs = await SharedPreferences.getInstance();
        cid = prefs.getString(ApiConfig.clubIdKey);
      } catch (_) {}
    }
    if (cid == null) {
      throw Exception('클럽 정보가 필요합니다');
    }

    try {
      await _apiService.post(
        '${ApiConfig.clubs}${ApiConfig.events}/rollback-import',
        data: {
          'eventId': int.tryParse(eventId) ?? eventId,
          'clubId': int.tryParse(cid) ?? cid,
          'includeGuests': includeGuests,
        },
      );
      // 로컬 캐시 반영
      _events.removeWhere((e) => e.id == eventId);
      notifyListeners();
      return true;
    } catch (e) {
      throw Exception('임포트 롤백에 실패했습니다: $e');
    }
  }

  // 참가자 관리 기능
  // 이벤트 참가자 목록 가져오기
  Future<List<Participant>> fetchEventParticipants(String eventId) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      // userId는 일부 라우트에서 필수이므로 prefs에서 읽어 포함
      final prefs = await SharedPreferences.getInstance();
      final userId = prefs.getString(ApiConfig.userIdKey);
      final payload = {
        'clubId': _clubId,
        if (userId != null) 'userId': userId,
        'eventId': eventId,
      };
      final data = await _apiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants/list',
        data: payload,
      );

      // 응답이 리스트이거나 객체 내 participants 키로 올 수 있어 모두 처리
      final List<dynamic> participantsData = (data is List)
          ? data
          : (data['participants'] ?? []);
      _participants = participantsData
          .map((participantData) => Participant.fromJson(participantData))
          .toList();

      _isLoading = false;
      notifyListeners();

      return _participants;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('참가자 목록을 불러오는데 실패했습니다: $e');
    }
  }

  // 참가자 추가
  Future<Participant> addParticipant(
    String eventId,
    Map<String, dynamic> participantData,
  ) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      // 참가자 페이로드 정규화: 상태/결제 상태 표준화 및 빈 문자열 null 처리
      final normalized = _sanitizeParticipantPayload(participantData);
      final payload = {'clubId': _clubId, ...normalized};
      final data = await _apiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants',
        data: payload,
      );

      final newParticipant = Participant.fromJson(data['participant']);

      _participants.add(newParticipant);

      _isLoading = false;
      notifyListeners();

      return newParticipant;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('참가자 추가에 실패했습니다: $e');
    }
  }

  // 참가자 정보 업데이트
  Future<Participant> updateParticipant(
    String eventId,
    String participantId,
    Map<String, dynamic> updates,
  ) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }

    // 업데이트 전에 clubId가 비어 있으면 SharedPreferences에서 보강 시도
    if (_clubId == null) {
      try {
        final prefs = await SharedPreferences.getInstance();
        final cid = prefs.getString(ApiConfig.clubIdKey);
        if (cid != null) {
          _clubId = cid;
        }
      } catch (_) {}
    }
    if (_clubId == null) {
      throw Exception('클럽 정보가 아직 설정되지 않았습니다. 클럽 선택 후 다시 시도해 주세요.');
    }

    _isLoading = true;
    notifyListeners();

    try {
      // 업데이트 페이로드도 동일한 규칙으로 정규화
      final normalized = _sanitizeParticipantPayload(updates);

      // 서버는 update 시 memberId가 필요함 → 로컬 캐시에서 찾아 보강
      String? memberId = normalized['memberId']?.toString();
      if (memberId == null || memberId.isEmpty) {
        final existing = _participants.firstWhere(
          (p) => p.id == participantId,
          orElse: () => Participant(
            id: participantId,
            eventId: eventId,
            memberId: '',
            status: 'pending',
            isPaid: false,
          ),
        );
        if (existing.memberId.isNotEmpty) {
          memberId = existing.memberId;
        }
      }
      if (memberId == null || memberId.isEmpty) {
        throw Exception('참가자 수정에 필요한 memberId를 찾을 수 없습니다. 목록 새로고침 후 다시 시도해 주세요.');
      }

      final payload = {
        'id': participantId,
        'clubId': _clubId,
        'memberId': memberId,
        ...normalized,
      };
      final data = await _apiService.put(
        '${ApiConfig.clubs}/events/$eventId/participants',
        data: payload,
      );

      final updatedParticipant = Participant.fromJson(data['participant']);

      // 참가자 목록 업데이트
      final index = _participants.indexWhere(
        (participant) => participant.id == participantId,
      );
      if (index >= 0) {
        _participants[index] = updatedParticipant;
      }

      _isLoading = false;
      notifyListeners();

      return updatedParticipant;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      final msg = e.toString();
      if (msg.contains('404') || msg.contains('Cannot PUT')) {
        throw Exception('참가자 업데이트 경로가 서버에서 지원되지 않거나 대상이 없습니다(404). 목록 새로고침 후 다시 시도하거나 서버 라우트를 확인해주세요. 상세: $e');
      }
      throw Exception('참가자 정보 업데이트에 실패했습니다: $e');
    }
  }

  // 참가자 페이로드 정규화 헬퍼
  Map<String, dynamic> _sanitizeParticipantPayload(Map<String, dynamic> data) {
    final Map<String, dynamic> out = Map<String, dynamic>.from(data);

    // 상태 표준화: 구버전/철자 변형 호환 + 서버 Enum 호환 임시 매핑
    final statusRaw = (out['status']?.toString().trim() ?? '').toLowerCase();
    if (statusRaw.isNotEmpty) {
      String status = statusRaw;
      if (status == 'cancelled') status = 'canceled';
      // 서버는 registered를 Enum으로 지원하지 않음 → 요청 전 'pending'으로 보정
      if (status == 'registered') status = 'pending';
      // 허용 목록: pending, confirmed, canceled (registered는 위에서 보정)
      if (!['pending', 'confirmed', 'canceled'].contains(status)) {
        // 허용 외 값은 안전하게 pending 처리
        status = 'pending';
      }
      out['status'] = status;
    }

    // 결제 상태 표준화: isPaid -> paymentStatus 변환 및 강제
    if (out.containsKey('isPaid') &&
        (out['paymentStatus'] == null ||
            (out['paymentStatus'].toString().isEmpty))) {
      final isPaidVal = out['isPaid'] == true;
      out['paymentStatus'] = isPaidVal ? 'paid' : 'unpaid';
    }
    final payRaw = (out['paymentStatus']?.toString().trim() ?? '')
        .toLowerCase();
    if (payRaw.isNotEmpty) {
      out['paymentStatus'] = (payRaw == 'paid') ? 'paid' : 'unpaid';
    }
    // 서버 스키마에 맞추기 위해 isPaid 키는 제거 (서버는 paymentStatus만 사용)
    out.remove('isPaid');

    // 빈 문자열을 명시적 null로 변환 (이름/이메일/전화/메모 등)
    out.updateAll((key, value) {
      if (value is String && value.trim().isEmpty) return null;
      return value;
    });

    return out;
  }

  // 참가자 삭제
  Future<bool> removeParticipant(String eventId, String participantId) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      await _apiService.delete(
        '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
      );

      // 참가자 목록에서 삭제
      _participants.removeWhere(
        (participant) => participant.id == participantId,
      );

      _isLoading = false;
      notifyListeners();

      return true;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('참가자 삭제에 실패했습니다: $e');
    }
  }

  // 참가자 상태 업데이트
  Future<Participant> updateParticipantStatus(
    String eventId,
    String participantId,
    String status,
  ) async {
    return updateParticipant(eventId, participantId, {'status': status});
  }

  // 점수 관리 기능
  // 이벤트 점수 목록 가져오기
  Future<List<Score>> fetchEventScores(String eventId) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }

    // 초기 진입 시점에 _clubId가 아직 설정되지 않았을 수 있으므로, 우선 SharedPreferences에서 시도
    if (_clubId == null) {
      try {
        final prefs = await SharedPreferences.getInstance();
        final cid = prefs.getString(ApiConfig.clubIdKey);
        if (cid != null) {
          _clubId = cid;
        }
      } catch (_) {}
    }

    // 그래도 없으면 점수는 조용히 빈 목록으로 처리하여 첫 진입 오류를 방지
    if (_clubId == null) {
      _isLoading = false;
      notifyListeners();
      return [];
    }

    _isLoading = true;
    notifyListeners();

    try {
      // 서버 라우트: GET /api/club/events/:id/scores
      // requireFeature 미들웨어가 clubId를 요구하므로 query로 전달
      final data = await _apiService.get(
        '${ApiConfig.clubs}/events/$eventId/scores',
        queryParameters: {'clubId': _clubId},
      );

      // 서버가 배열 자체를 반환하거나 { scores: [...] } 형태로 반환할 수 있어 모두 처리
      final List<dynamic> scoresData = (data is List)
          ? data
          : (data['scores'] ?? []);
      _scores = scoresData
          .map((scoreData) => Score.fromJson(scoreData))
          .toList();

      _isLoading = false;
      notifyListeners();

      return _scores;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      final msg = e.toString();
      // 서버 미들웨어에서 clubId 누락 시 400을 반환할 수 있는데, 초기 진입에서는 조용히 무시하고 빈 목록 반환
      if (msg.contains('400') && (msg.contains('클럽 정보가 필요합니다') || msg.contains('club'))) {
        return [];
      }
      throw Exception('점수 목록을 불러오는데 실패했습니다: $e');
    }
  }

  // 점수 추가
  Future<Score> addScore(String eventId, Map<String, dynamic> scoreData) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      // 빈 문자열을 명시적 null로 변환해 서버에서 정확히 처리되도록 함
      final sanitized = Map<String, dynamic>.from(scoreData)..updateAll((key, value) {
        if (value is String && value.trim().isEmpty) return null;
        return value;
      });

      final data = await _apiService.post(
        '${ApiConfig.clubs}/events/$eventId/scores',
        data: {
          'clubId': _clubId,
          ...sanitized,
        },
      );

      final newScore = Score.fromJson(data['score']);

      _scores.add(newScore);

      _isLoading = false;
      notifyListeners();

      return newScore;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('점수 추가에 실패했습니다: $e');
    }
  }

  // 점수 업데이트
  Future<Score> updateScore(
    String eventId,
    String scoreId,
    Map<String, dynamic> updates,
  ) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      // 캐시에서 대상 점수 찾아 gameNumber 확보
      final Score current = _scores.firstWhere(
        (s) => s.id == scoreId,
        orElse: () => Score(
          id: scoreId,
          memberId: '',
          eventId: eventId,
          clubId: _clubId ?? '',
          frames: const [],
          totalScore: 0,
          date: DateTime.now(),
        ),
      );

      // 업데이트 시에도 빈 문자열을 null로 변환
      final sanitized = Map<String, dynamic>.from(updates)..updateAll((key, value) {
        if (value is String && value.trim().isEmpty) return null;
        return value;
      });

      // 서버 호환: totalScore -> score 매핑, 필수 필드 포함
      final Map<String, dynamic> payload = {
        'clubId': _clubId,
        'eventId': eventId,
        'scoreId': scoreId,
        'gameNumber': sanitized['gameNumber'] ?? current.gameNumber,
        'score': sanitized['score'] ?? sanitized['totalScore'],
        // 날짜 보존: 서버가 업데이트 시 현재 시각으로 덮어쓰지 않도록 기존 날짜 전송
        'date': (sanitized['date'] ?? current.date.toIso8601String()),
        // 참고: 일부 라우트는 totalScore도 허용할 수 있어 함께 전달(서버가 무시해도 무해)
        if (sanitized.containsKey('totalScore')) 'totalScore': sanitized['totalScore'],
        if (sanitized.containsKey('handicap')) 'handicap': sanitized['handicap'],
        if (sanitized.containsKey('notes')) 'notes': sanitized['notes'],
        if (sanitized.containsKey('frames')) 'frames': sanitized['frames'],
      }..removeWhere((key, value) => value == null);

      final data = await _apiService.put(
        '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
        data: payload,
      );

      final updatedScore = Score.fromJson(data['score']);

      // 점수 목록 업데이트
      final index = _scores.indexWhere((score) => score.id == scoreId);
      if (index >= 0) {
        _scores[index] = updatedScore;
      }

      _isLoading = false;
      notifyListeners();

      return updatedScore;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('점수 업데이트에 실패했습니다: $e');
    }
  }

  // 점수 삭제
  Future<bool> deleteScore(String eventId, String scoreId) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      await _apiService.delete(
        '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
      );

      // 점수 목록에서 삭제
      _scores.removeWhere((score) => score.id == scoreId);

      _isLoading = false;
      notifyListeners();

      return true;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('점수 삭제에 실패했습니다: $e');
    }
  }

  // 팀 관리 기능
  // 이벤트 팀 목록 가져오기
  Future<List<Team>> fetchEventTeams(String eventId) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }

    try {
      // 백엔드 라우트: GET /api/club/events/:id/teams
      final data = await _apiService.get(
        '${ApiConfig.clubs}/events/$eventId/teams',
        queryParameters: { 'clubId': _clubId },
      );

      final List<dynamic> teamsData = (data is List) ? data : (data['teams'] ?? []);
      // 서버가 'memberIds' 또는 'members' 배열(참가자/멤버 참조)로 반환할 수 있어 모두 처리
      _teams = teamsData.map((teamData) {
        // 표준 경로: Team.fromJson에서 memberIds가 바로 존재
        final hasMemberIds = teamData is Map && teamData['memberIds'] != null;
        if (hasMemberIds) {
          return Team.fromJson(teamData as Map<String, dynamic>);
        }

        // 대체 경로: members 배열로 내려오는 경우 각 요소에서 memberId 또는 participantId를 memberId로 매핑
        if (teamData is Map && teamData['members'] is List) {
          final List members = teamData['members'] as List;
          final List<String> memberIds = [];
          for (final m in members) {
            if (m is Map) {
              // 우선 memberId가 있으면 사용
              final mid = m['memberId']?.toString();
              if (mid != null && mid.isNotEmpty) {
                memberIds.add(mid);
                continue;
              }
              // 없으면 participantId(id)로 참가자 캐시에서 memberId를 조회
              final pid = (m['participantId'] ?? m['id'])?.toString();
              if (pid != null && pid.isNotEmpty) {
                try {
                  final p = _participants.firstWhere((pp) => pp.id == pid);
                  if (p.memberId.isNotEmpty) memberIds.add(p.memberId);
                } catch (_) {}
              }
            }
          }
          return Team(
            id: teamData['id']?.toString() ?? '',
            eventId: teamData['eventId']?.toString() ?? eventId,
            name: teamData['name']?.toString() ?? '',
            memberIds: memberIds,
            description: teamData['description']?.toString(),
            createdAt: teamData['createdAt'] != null ? DateTime.tryParse(teamData['createdAt'].toString()) : null,
            updatedAt: teamData['updatedAt'] != null ? DateTime.tryParse(teamData['updatedAt'].toString()) : null,
          );
        }

        // 알 수 없는 포맷은 방어적으로 기본 파싱
        return Team.fromJson(Map<String, dynamic>.from(teamData as Map));
      }).toList();

      notifyListeners();
      return _teams;
    } catch (e) {
      // 서버 미지원/404 시 로컬 저장소에서 로드 시도
      try {
        final prefs = await SharedPreferences.getInstance();
        final key = 'local_teams_' + eventId;
        final raw = prefs.getString(key);
        if (raw != null && raw.isNotEmpty) {
          final List<dynamic> jsonList = jsonDecode(raw);
          _teams = jsonList.map((t) => Team.fromJson(t)).toList();
          notifyListeners();
          return _teams;
        }
      } catch (_) {}
      throw Exception('팀 목록을 불러오는데 실패했습니다: $e');
    }
  }

  // 이벤트에 팀이 있는지 확인 (간략 조회)
  Future<bool> hasEventTeams(String eventId) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }

    try {
      // 백엔드 라우트: GET /api/club/events/:id/teams
      final data = await _apiService.get(
        '${ApiConfig.clubs}/events/$eventId/teams',
        queryParameters: { 'clubId': _clubId },
      );

      final List<dynamic> teamsData = (data is List) ? data : (data['teams'] ?? []);
      return teamsData.isNotEmpty;
    } catch (e) {
      print('팀 확인 실패: $e');
      return false;
    }
  }

  // 팀 생성
  Future<List<Team>> generateTeams(
    String eventId,
    Map<String, dynamic> teamConfig,
  ) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      final data = await _apiService.post(
        '${ApiConfig.clubs}/events/$eventId/teams/generate',
        data: teamConfig,
      );

      final List<dynamic> teamsData = data['teams'] ?? [];
      _teams = teamsData.map((teamData) => Team.fromJson(teamData)).toList();

      _isLoading = false;
      notifyListeners();

      return _teams;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('팀 생성에 실패했습니다: $e');
    }
  }

  // 팀 저장
  Future<bool> saveTeams(String eventId, List<Team> teams) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      await _apiService.post(
        '${ApiConfig.clubs}/events/$eventId/teams',
        data: {
          'clubId': _clubId,
          'teams': teams.map((team) => team.toJson()).toList(),
        },
      );

      _teams = teams;

      _isLoading = false;
      notifyListeners();

      return true;
    } catch (e) {
      // 서버 미지원/404 시 로컬로 저장
      try {
        final prefs = await SharedPreferences.getInstance();
        final key = 'local_teams_' + eventId;
        final raw = jsonEncode(teams.map((t) => t.toJson()).toList());
        await prefs.setString(key, raw);
        _teams = teams;
        _isLoading = false;
        notifyListeners();
        return true;
      } catch (e2) {
        _isLoading = false;
        notifyListeners();
        throw Exception('팀 저장에 실패했습니다(로컬 폴백도 실패): $e2');
      }
    }
  }

  // 파일에서 이벤트 일괄 생성
  Future<List<Event>> createEventsFromFile(
    List<Map<String, dynamic>> eventsData,
  ) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      final List<Event> createdEvents = [];

      // 각 이벤트 데이터에 클럽 ID 추가
      for (var eventData in eventsData) {
        eventData['clubId'] = _clubId;

        // null 값 처리 - 빈 문자열을 명시적 null로 변환
        eventData.forEach((key, value) {
          if (value is String && value.isEmpty) {
            eventData[key] = null;
          }
        });

        // 이벤트 생성 API 호출
        final data = await _apiService.post(
          '${ApiConfig.clubs}/events',
          data: eventData,
        );

        final newEvent = Event.fromJson(data['event']);
        createdEvents.add(newEvent);
        _events.add(newEvent);
      }

      _isLoading = false;
      notifyListeners();

      return createdEvents;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('파일에서 이벤트 생성에 실패했습니다: $e');
    }
  }

  // 이벤트 복제
  Future<Event> cloneEvent(String eventId, {String? newName}) async {
    if (_token == null || _clubId == null) {
      throw Exception('인증 또는 클럽 정보가 필요합니다');
    }

    _isLoading = true;
    notifyListeners();

    try {
      // 원본 이벤트 정보 가져오기
      final originalEvent = await fetchEventById(eventId);

      // 복제할 이벤트 데이터 준비
      final Map<String, dynamic> cloneData = {
        'clubId': _clubId,
        'title': newName ?? '${originalEvent.title} (복사본)',
        'description': originalEvent.description,
        'location': originalEvent.location,
        'type': originalEvent.type,
        'status': 'draft', // 복제된 이벤트는 초안 상태로 시작
        'startDate': originalEvent.startDate.toIso8601String(),
        'endDate': originalEvent.endDate?.toIso8601String(),
        'maxParticipants': originalEvent.maxParticipants,
        'participantFee': originalEvent.participantFee,
        'gameCount': originalEvent.gameCount,
        'publicHash': originalEvent.publicHash,
        'accessPassword': originalEvent.accessPassword,
        'isActive': true,
      };

      // 이벤트 생성 API 호출
      final data = await _apiService.post(
        '${ApiConfig.clubs}/events',
        data: cloneData,
      );

      final newEvent = Event.fromJson(data['event']);
      _events.add(newEvent);

      _isLoading = false;
      notifyListeners();

      return newEvent;
    } catch (e) {
      _isLoading = false;
      notifyListeners();
      throw Exception('이벤트 복제에 실패했습니다: $e');
    }
  }

  // =============================
  // 파일 업로드 기반 일괄 생성 플로우 (Vue 호환)
  // =============================

  // 파일 업로드: /club/events/upload
  Future<Map<String, dynamic>> uploadEventFile(File file) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }
    try {
      // clubId 보강: 아직 설정 전인 경우 SharedPreferences에서 로드
      if (_clubId == null) {
        try {
          final prefs = await SharedPreferences.getInstance();
          final cid = prefs.getString(ApiConfig.clubIdKey);
          if (cid != null && cid.isNotEmpty) {
            _clubId = cid;
          }
        } catch (_) {}
      }
      if (_clubId == null || _clubId!.isEmpty) {
        throw Exception('클럽 정보가 필요합니다. 클럽을 먼저 선택해 주세요.');
      }
      final fileName = file.path.split('/').last;
      final formData = FormData.fromMap({
        'file': await MultipartFile.fromFile(file.path, filename: fileName),
        'clubId': _clubId,
      });
      final data = await _apiService.post(
        '${ApiConfig.clubs}${ApiConfig.events}/upload?clubId=${Uri.encodeComponent(_clubId!)}',
        data: formData,
      );
      if (data is Map) {
        final map = Map<String, dynamic>.from(data as Map);
        // 서버 응답: { message, file: { filename, originalname, ... } }
        if (map['file'] is Map) {
          final fm = Map<String, dynamic>.from(map['file'] as Map);
          final out = <String, dynamic>{
            'filename': fm['filename']?.toString(),
          };
          if (fm.containsKey('originalname')) {
            final orig = fm['originalname']?.toString();
            out['originalname'] = orig;
            // URL 안전 인코딩 버전 (다운로드 링크 등에 사용)
            if (orig != null) {
              out['originalnameEncoded'] = Uri.encodeComponent(orig);
              // 유니코드 이스케이프 버전 (로그/표시 안전)
              out['originalnameUnicode'] = _toUnicodeEscape(orig);
            }
          }
          if (fm.containsKey('mimetype')) out['mimetype'] = fm['mimetype'];
          if (fm.containsKey('size')) out['size'] = fm['size'];
          if (fm.containsKey('path')) out['path'] = fm['path'];
          if (fm.containsKey('clubId')) out['clubId'] = fm['clubId'];
          return out;
        }
        // 혹시 서버가 평면으로 줄 경우도 방어
        if (map.containsKey('filename')) return Map<String, dynamic>.from(map);
      }
      return {'filename': data.toString()};
    } catch (e) {
      throw Exception('파일 업로드 실패: $e');
    }
  }

  // 비 ASCII 문자들을 \uXXXX 형태로 이스케이프
  String _toUnicodeEscape(String input) {
    final StringBuffer sb = StringBuffer();
    for (final int codeUnit in input.codeUnits) {
      if (codeUnit >= 0x20 && codeUnit <= 0x7E) {
        sb.writeCharCode(codeUnit);
      } else {
        sb.write('\\u');
        sb.write(codeUnit.toRadixString(16).padLeft(4, '0'));
      }
    }
    return sb.toString();
  }

  // 파일 파싱 미리보기: /club/events/parse-preview
  Future<Map<String, dynamic>> parseEventFile({
    required String filename,
    String? sheetName,
  }) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }
    try {
      final payload = <String, dynamic>{
        'filename': filename,
        if (_clubId != null) 'clubId': _clubId,
        if (sheetName != null) 'sheetName': sheetName,
      };
      final data = await _apiService.post(
        '${ApiConfig.clubs}${ApiConfig.events}/parse-preview',
        data: payload,
      );
      // 서버는 { message, data: { participants: [...], gameCount, inferredEventInfo, sheets? } } 형태로 응답
      // UI는 최상위에서 participants 등을 기대하므로 data 래퍼를 풀어 반환
      if (data is! Map) return {'raw': data};
      final map = Map<String, dynamic>.from(data);
      if (map['data'] is Map) {
        return Map<String, dynamic>.from(map['data']);
      }
      return map;
    } catch (e) {
      throw Exception('파일 미리보기 파싱 실패: $e');
    }
  }

  // 파일 데이터 저장: /club/events/save-file-data
  Future<Map<String, dynamic>> saveEventFileData({
    required Map<String, dynamic> eventData,
    required List<dynamic> participants,
  }) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }
    try {
      // 보장: clubId 확보 (메모리에 없으면 SharedPreferences에서 로드)
      if (_clubId == null) {
        try {
          final prefs = await SharedPreferences.getInstance();
          final cid = prefs.getString(ApiConfig.clubIdKey);
          if (cid != null && cid.isNotEmpty) {
            _clubId = cid;
          }
        } catch (_) {}
      }

      // clubId 보강 및 날짜/빈 문자열 정리
      final payloadEvent = Map<String, dynamic>.from(eventData);
      payloadEvent['clubId'] = _clubId ?? payloadEvent['clubId'];
      payloadEvent.updateAll((key, value) {
        if (value is DateTime) return value.toIso8601String();
        if (value is String && value.trim().isEmpty) return null;
        return value;
      });

      // 서버 ENUM 호환을 위한 상태/유형 정규화
      String? rawStatus = payloadEvent['status']?.toString();
      if (rawStatus != null && rawStatus.isNotEmpty) {
        final s = rawStatus.toLowerCase();
        if (['active', '활성'].contains(s)) payloadEvent['status'] = 'active';
        else if (['ready', '대기', '준비', 'draft'].contains(s)) payloadEvent['status'] = 'ready';
        else if (['completed', '완료', 'done', 'finished'].contains(s)) payloadEvent['status'] = 'completed';
        else if (['canceled', 'cancelled', '취소'].contains(s)) payloadEvent['status'] = 'canceled';
        else if (['deleted', '삭제'].contains(s)) payloadEvent['status'] = 'deleted';
        else payloadEvent['status'] = 'ready';
      }

      String? rawType = payloadEvent['eventType']?.toString();
      if (rawType != null && rawType.isNotEmpty) {
        final t = rawType.toLowerCase();
        if (['regular','일반'].contains(t)) payloadEvent['eventType'] = 'regular';
        else if (['lightning','번개'].contains(t)) payloadEvent['eventType'] = 'lightning';
        else if (['practice','연습'].contains(t)) payloadEvent['eventType'] = 'practice';
        else if (['exchange','교류전'].contains(t)) payloadEvent['eventType'] = 'exchange';
        else if (['event','행사'].contains(t)) payloadEvent['eventType'] = 'event';
        else payloadEvent['eventType'] = 'other';
      }

      // 참가자 페이로드 정리: teamNumber를 숫자로 정규화, handicap 숫자 보정
      final List<dynamic> sanitizedParticipants = participants.map((p) {
        if (p is Map) {
          final out = Map<String, dynamic>.from(p);
          // teamNumber: '2팀' 같은 문자열에서 숫자만 추출
          final tn = out['teamNumber'];
          if (tn != null) {
            final m = RegExp(r"\d+").firstMatch(tn.toString());
            out['teamNumber'] = m != null ? int.tryParse(m.group(0)!) : null;
          }
          // handicap: 숫자 보정
          if (out.containsKey('handicap')) {
            final hv = out['handicap'];
            out['handicap'] = (hv is num)
                ? hv.toInt()
                : int.tryParse(hv?.toString() ?? '') ?? 0;
          }
          return out;
        }
        return p;
      }).toList();

      final data = await _apiService.post(
        '${ApiConfig.clubs}${ApiConfig.events}/save-file-data',
        data: {
          'eventData': payloadEvent,
          'participants': sanitizedParticipants,
          if (_clubId != null) 'clubId': _clubId,
        },
      );
      if (data is! Map) return {'raw': data};
      return Map<String, dynamic>.from(data);
    } catch (e) {
      throw Exception('파일 데이터 저장 실패: $e');
    }
  }

  // 업로드 파일 삭제: /club/events/delete-file
  Future<bool> deleteUploadedFile(String filename) async {
    if (_token == null) {
      throw Exception('인증 정보가 필요합니다');
    }
    try {
      await _apiService.post(
        '${ApiConfig.clubs}${ApiConfig.events}/delete-file',
        data: {
          'filename': filename,
          if (_clubId != null) 'clubId': _clubId,
        },
      );
      return true;
    } catch (e) {
      // 실패해도 치명적이지 않음
      if (kDebugMode) {
        print('임시 파일 삭제 실패: $e');
      }
      return false;
    }
  }

}
