import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:html/parser.dart' as html_parser;
import 'package:html/dom.dart' as dom;

typedef void OnProgress(String status, double? percent);

class SegaService {
  static Future<Map<String, dynamic>> fetchAndSync({
    required Region region,
    required String segaId,
    required String password,
    OnProgress? onProgress,
  }) async {
    print("[SegaService] fetchAndSync 开始执行，区域：${region.name}");
    print("[SegaService] SegaID：$segaId");

    final MaimaiNetClient client;
    if (region == Region.jp) {
      client = MaimaiNETJpClient();
      print("[SegaService] 使用日服客户端");
    } else {
      client = MaimaiNETIntlClient();
      print("[SegaService] 使用国际服客户端");
    }

    try {
      onProgress?.call("正在登录 Sega 账号...", null);
      await client.login(AuthParams(id: segaId, password: password));
      print("[SegaService] 登录成功");

      onProgress?.call("登录成功，获取用户信息...", null);
      final userInfo = await client.fetchUserInfo();
      print("[SegaService] 用户信息获取成功：${userInfo.name}");

      onProgress?.call("开始拉取歌曲成绩...", null);
      final musicRecords = await client.fetchMusicRecords(onProgress: onProgress);
      print("[SegaService] 歌曲成绩获取完成，总数：${musicRecords.length}");

      return {
        "user": userInfo.toJson(),
        "music": musicRecords.map((e) => e.toJson()).toList(),
        "region": region.name,
      };
    } catch (e) {
      print("[SegaService] fetchAndSync 发生错误：$e");
      rethrow;
    }
  }

  static Future<Map<String, dynamic>> verifyOnlyLogin({
    required Region region,
    required String segaId,
    required String password,
  }) async {
    print("[SegaService] verifyOnlyLogin 开始验证登录，区域：${region.name}");
    print("[SegaService] 验证账号：$segaId");

    final MaimaiNetClient client;
    if (region == Region.jp) {
      client = MaimaiNETJpClient();
    } else {
      client = MaimaiNETIntlClient();
    }
    try {
      await client.login(AuthParams(id: segaId, password: password));
      print("[SegaService] 验证登录成功");
      final userInfo = await client.fetchUserInfo();
      print("[SegaService] 验证成功，用户名：${userInfo.name}");

      return {
        "user": userInfo.toJson(),
        "music": [],
        "region": region.name,
      };
    } catch (e) {
      print("[SegaService] 验证登录失败：$e");
      rethrow;
    }
  }
}

enum Region { jp, intl }

class NetImportError implements Exception {
  final String code;
  final String message;
  NetImportError(this.code, [String? msg]) : message = msg ?? code;
  @override
  String toString() => 'NetImportError($code): $message';
}

class AuthParams {
  final String id;
  final String password;
  AuthParams({required this.id, required this.password});
}

class SheetInfo {
  final String songId;
  final String type;
  final String difficulty;

  SheetInfo({required this.songId, required this.type, required this.difficulty});

  Map<String, dynamic> toJson() => {
    'songId': songId,
    'type': type,
    'difficulty': difficulty,
  };
}

class DxScore {
  final int achieved;
  final int total;
  DxScore({required this.achieved, required this.total});

  Map<String, dynamic> toJson() => {
    'achieved': achieved,
    'total': total,
  };
}

class Achievement {
  final int rate;
  final DxScore dxScore;
  final List<String> flags;

  Achievement({required this.rate, required this.dxScore, required this.flags});

  Map<String, dynamic> toJson() => {
    'rate': rate,
    'dxScore': dxScore.toJson(),
    'flags': flags,
  };
}

class MusicRecord {
  final SheetInfo sheet;
  final Achievement achievement;

  MusicRecord({required this.sheet, required this.achievement});

  Map<String, dynamic> toJson() => {
    'sheet': sheet.toJson(),
    'achievement': achievement.toJson(),
  };
}

class UserInfo {
  final String avatar;
  final String name;
  final Map<String, String> trophy;
  final String courseIcon;
  final String classIcon;
  final String plateUrl;

  UserInfo({
    required this.avatar,
    required this.name,
    required this.trophy,
    required this.courseIcon,
    required this.classIcon,
    required this.plateUrl,
  });

  Map<String, dynamic> toJson() => {
    'avatar': avatar,
    'name': name,
    'trophy': trophy,
    'courseIcon': courseIcon,
    'classIcon': classIcon,
    'plateUrl': plateUrl,
  };
}

class URLs {
  static const Map<String, String> INTL = {
    "LOGIN_PAGE": "https://lng-tgk-aime-gw.am-all.net/common_auth/login?site_id=maimaidxex&redirect_url=https://maimaidx-eng.com/maimai-mobile/&back_url=https://maima.sega.com/",
    "LOGIN_ENDPOINT": "https://lng-tgk-aime-gw.am-all.net/common_auth/login/sid",
    "RECORD_RECENT_PAGE": "https://maimaidx-eng.com/maimai-mobile/record",
    "RECORD_MUSICS_PAGE": "https://maimaidx-eng.com/maimai-mobile/record/musicGenre/search/",
    "HOME": "https://maimaidx-eng.com/maimai-mobile/home/",
    "NAMEPLATE": "https://maimaidx-eng.com/maimai-mobile/collection/nameplate/",
  };

  static const Map<String, String> JP = {
    "LOGIN_PAGE": "https://maimaidx.jp/maimai-mobile/",
    "LOGIN_ENDPOINT": "https://maimaidx.jp/maimai-mobile/submit/",
    "LOGIN_AIMELIST": "https://maimaidx.jp/maimai-mobile/aimeList/",
    "LOGIN_AIMELIST_SUBMIT": "https://maimaidx.jp/maimai-mobile/aimeList/submit/?idx=0",
    "HOMEPAGE": "https://maimaidx.jp/maimai-mobile/home/",
    "RECORD_RECENT_PAGE": "https://maimaidx.jp/maimai-mobile/record",
    "RECORD_MUSICS_PAGE": "https://maimaidx.jp/maimai-mobile/record/musicGenre/search/",
    "NAMEPLATE": "https://maimaidx.jp/maimai-mobile/collection/nameplate/",
  };

  static const List<String> ERROR_URLS = [
    "https://maimaidx-eng.com/maimai-mobile/error/",
    "https://maimaidx.jp/maimai-mobile/error/"
  ];

  static const List<String> MAINTENANCE_TEXTS = [
    "定期メンテナンス中です",
    "Sorry, servers are under maintenance."
  ];
}

abstract class MaimaiNetClient {
  late Dio dio;
  late Map<String, String> urls;

  final Map<String, String> _cookies = {};

  MaimaiNetClient() {
    dio = Dio();
    _setupDio();
    print("[MaimaiNetClient] 客户端初始化完成");
  }

  void _setupDio() {
    print("[MaimaiNetClient] 配置 Dio");

    (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
      final client = HttpClient();
      client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
      return client;
    };

    dio.interceptors.add(InterceptorsWrapper(
        onRequest: (options, handler) {
          print("[DIO] 请求：${options.method} ${options.uri}");
          if (_cookies.isNotEmpty) {
            final cookieStr = _cookies.entries.map((e) => '${e.key}=${e.value}').join('; ');
            options.headers['Cookie'] = cookieStr;
          }
          return handler.next(options);
        },
        onResponse: (response, handler) {
          print("[DIO] 响应：${response.statusCode} ${response.requestOptions.uri}");
          final setCookies = response.headers['set-cookie'];
          if (setCookies != null) {
            for (var cookieHeader in setCookies) {
              if (cookieHeader.contains('=')) {
                final parts = cookieHeader.split(';')[0].split('=');
                if (parts.length >= 2) {
                  final key = parts[0].trim();
                  final value = parts.sublist(1).join('=').trim();
                  if (key.isNotEmpty) {
                    _cookies[key] = value;
                  }
                }
              }
            }
          }
          return handler.next(response);
        },
        onError: (DioException e, handler) {
          print("[DIO] 请求错误：$e");
          return handler.next(e);
        }));

    dio.options.headers = {
      "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
      "Accept-Language": "ja;q=0.9,en;q=0.8",
      "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
      "Sec-Fetch-Dest": "document",
      "Sec-Fetch-Mode": "navigate",
      "Upgrade-Insecure-Requests": "1",
    };

    dio.options.connectTimeout = const Duration(seconds: 10);
    dio.options.receiveTimeout = const Duration(seconds: 10);
    dio.options.followRedirects = false;
    dio.options.maxRedirects = 0;
    dio.options.validateStatus = (status) => true;
  }

  Future<dom.Document> fetchAsSoupWithRedirects(String url, {Map<String, dynamic>? queryParameters}) async {
    print("[fetchAsSoup] 开始请求（支持重定向）：$url 参数：$queryParameters");

    String currentUrl = url;
    int redirectCount = 0;
    const maxRedirects = 5;

    while (redirectCount < maxRedirects) {
      try {
        final response = await dio.get(currentUrl, queryParameters: queryParameters);

        if (response.data is String) {
          checkMaintenance(response.data.toString());
        }

        if ([301, 302, 303, 307, 308].contains(response.statusCode)) {
          final location = response.headers.value('location');
          if (location != null) {
            print("[fetchAsSoup] 重定向 -> $location");
            currentUrl = location;
            redirectCount++;
            continue;
          } else {
            print("[fetchAsSoup] 重定向无 location");
            throw NetImportError("REDIRECT_WITHOUT_LOCATION");
          }
        }

        if (response.statusCode! >= 400) {
          print("[fetchAsSoup] HTTP 错误：${response.statusCode}");
          throw NetImportError("HTTP_ERROR_${response.statusCode}", "Failed to fetch $url");
        }

        print("[fetchAsSoup] 请求完成，解析 HTML");
        return html_parser.parse(response.data);
      } on DioException catch (e) {
        print("[fetchAsSoup] Dio 异常：$e");
        rethrow;
      }
    }
    print("[fetchAsSoup] 重定向次数过多");
    throw NetImportError("TOO_MANY_REDIRECTS");
  }

  Future<dom.Document> fetchAsSoup(String url, {Map<String, dynamic>? queryParameters}) async {
    return fetchAsSoupWithRedirects(url, queryParameters: queryParameters);
  }

  void checkMaintenance(String text) {
    for (final maintText in URLs.MAINTENANCE_TEXTS) {
      if (text.contains(maintText)) {
        print("[检查维护] 检测到服务器维护！");
        throw NetImportError("NET_MAINTENANCE");
      }
    }
  }

  Future<void> login(AuthParams auth);
  Future<UserInfo> fetchUserInfo();
  Future<List<MusicRecord>> fetchMusicRecords({OnProgress? onProgress});

  SheetInfo? parseSheetInfo(dom.Element div) {
    final songIdEl = div.querySelector(".music_name_block");
    if (songIdEl == null) return null;
    final songId = songIdEl.text.trim();

    String? type;
    if (div.querySelector(".music_kind_icon_dx._btn_on") != null) {
      type = "dx";
    } else if (div.querySelector(".music_kind_icon_standard._btn_on") != null) {
      type = "standard";
    } else {
      final typeIcon = div.querySelector(".music_kind_icon");
      if (typeIcon != null) {
        final src = typeIcon.attributes['src'] ?? '';
        if (src.contains('music_dx.png')) type = 'dx';
        else if (src.contains('music_standard.png')) type = 'standard';
      }
    }

    String? difficulty;
    final diffIcon = div.querySelector(".h_20.f_l");
    if (diffIcon != null) {
      final src = diffIcon.attributes['src'] ?? '';
      final match = RegExp(r'diff_(.*)\.png').firstMatch(src);
      if (match != null) {
        difficulty = match.group(1);
      }
    }

    if (difficulty == "utage") type = "utage";
    if (songId.isEmpty || type == null || difficulty == null) return null;

    return SheetInfo(songId: songId, type: type, difficulty: difficulty);
  }

  Achievement parseAchievement(dom.Element div) {
    final rateEl = div.querySelector(".music_score_block.w_112");
    final rateStr = rateEl?.text.trim().replaceAll('%', '').replaceAll('.', '') ?? '0';
    final rate = int.tryParse(rateStr) ?? 0;

    final dxScoreEl = div.querySelector(".music_score_block.w_190");
    final dxScoreText = dxScoreEl?.text.trim() ?? "0 / 0";
    final parts = dxScoreText.split('/');
    final achieved = int.tryParse(parts[0].trim().replaceAll(',', '')) ?? 0;
    final total = parts.length > 1 ? (int.tryParse(parts[1].trim().replaceAll(',', '')) ?? 0) : 0;

    final flags = <String>[];
    final flagImages = div.querySelectorAll("form img.f_r");
    final flagMatchers = {
      "fullCombo": "fc.png",
      "fullCombo+": "fcplus.png",
      "allPerfect": "ap.png",
      "allPerfect+": "applus.png",
      "syncPlay": "sync.png",
      "fullSync": "fs.png",
      "fullSync+": "fsplus.png",
      "fullSyncDX": "fsd.png",
      "fullSyncDX+": "fsdplus.png",
    };

    for (final img in flagImages) {
      final src = img.attributes['src'] ?? '';
      for (final entry in flagMatchers.entries) {
        if (src.contains(entry.value)) {
          flags.add(entry.key);
          break;
        }
      }
    }

    return Achievement(
      rate: rate,
      dxScore: DxScore(achieved: achieved, total: total),
      flags: flags,
    );
  }
}

class MaimaiNETJpClient extends MaimaiNetClient {
  MaimaiNETJpClient() {
    urls = URLs.JP;
    print("[MaimaiNETJpClient] 日服客户端初始化");
  }

  @override
  Future<void> login(AuthParams auth) async {
    print("[日服客户端] 开始登录流程");

    final soup = await fetchAsSoup(urls["LOGIN_PAGE"]!);
    final tokenInput = soup.querySelector("input[name='token']");
    if (tokenInput == null) {
      print("[日服客户端] 未找到 token，登录失败");
      throw NetImportError("TOKEN_ERROR");
    }
    final token = tokenInput.attributes['value'];
    print("[日服客户端] 获取 token 成功：$token");

    final loginData = {
      "segaId": auth.id,
      "password": auth.password,
      "save_cookie": "on",
      "token": token!,
    };

    final response = await dio.post(
      urls["LOGIN_ENDPOINT"]!,
      data: FormData.fromMap(loginData),
      options: Options(
        contentType: Headers.formUrlEncodedContentType,
      ),
    );

    final location = response.headers.value('location');
    if (location != null && URLs.ERROR_URLS.any((e) => location.contains(e))) {
      print("[日服客户端] 账号或密码错误");
      throw NetImportError("INVALID_CREDENTIALS");
    }

    print("[日服客户端] 登录接口请求成功，处理跳转");
    await fetchAsSoup(urls["LOGIN_AIMELIST"]!);
    await fetchAsSoup(urls["LOGIN_AIMELIST_SUBMIT"]!);
    await fetchAsSoup(urls["HOMEPAGE"]!);
    print("[日服客户端] 登录流程全部完成");
  }

  @override
  Future<UserInfo> fetchUserInfo() async {
    print("[日服客户端] 获取用户信息");
    final soup = await fetchAsSoup(urls["HOMEPAGE"]!);
    final avatarImg = soup.querySelector('img[loading="lazy"].w_112.f_l');
    final avatar = avatarImg?.attributes['src'] ?? '';
    final nameEl = soup.querySelector(".name_block");
    final name = nameEl?.text.trim() ?? '';

    String trophyClass = "Normal";
    String trophyName = "";
    final trophyBlock = soup.querySelector("div.trophy_block");
    if (trophyBlock != null) {
      for (final c in trophyBlock.classes) {
        if (["trophy_Normal", "trophy_Silver", "trophy_Gold", "trophy_Rainbow"].contains(c)) {
          trophyClass = c.replaceFirst('trophy_', '');
          break;
        }
      }
      final trophyInner = trophyBlock.querySelector(".trophy_inner_block span");
      if (trophyInner != null) trophyName = trophyInner.text.trim();
    }

    final courseImg = soup.querySelector('img[src*="course_rank_"]');
    final courseIcon = courseImg?.attributes['src'] ?? '';
    final classImg = soup.querySelector('img[src*="class_rank_"]');
    final classIcon = classImg?.attributes['src'] ?? '';

    String plateUrl = "";
    try {
      print("[日服客户端] 获取铭牌");
      final plateSoup = await fetchAsSoup(urls["NAMEPLATE"]!);
      final plateImg = plateSoup.querySelector('img[loading="lazy"].w_396.m_r_10');
      if (plateImg != null) plateUrl = plateImg.attributes['src'] ?? '';
    } catch (e) {
      print("[日服客户端] 获取铭牌失败：$e");
    }

    print("[日服客户端] 用户信息解析完成：$name");
    return UserInfo(
      avatar: avatar,
      name: name,
      trophy: {'rarity': trophyClass, 'text': trophyName},
      courseIcon: courseIcon,
      classIcon: classIcon,
      plateUrl: plateUrl,
    );
  }

  // ====================== 【修复点】只拉一遍，不统计 ======================
  @override
  Future<List<MusicRecord>> fetchMusicRecords({OnProgress? onProgress}) async {
    print("[日服客户端] 开始获取所有歌曲成绩（修复版：仅拉取1次）");
    final records = <MusicRecord>[];

    final difficulties = [
      ("0", "Basic"),
      ("1", "Advanced"),
      ("2", "Expert"),
      ("3", "Master"),
      ("4", "Remaster"),
      ("10", "Utage"),
    ];

    for (final diff in difficulties) {
      try {
        onProgress?.call("正在拉取：${diff.$2}", null);
        final url = urls["RECORD_MUSICS_PAGE"]!;
        final soup = await fetchAsSoup(url, queryParameters: {"genre": "99", "diff": diff.$1});
        final items = soup.querySelectorAll(".w_450.m_15.p_r.f_0");

        for (final item in items) {
          final sheet = parseSheetInfo(item);
          if (sheet != null) {
            final achievement = parseAchievement(item);
            records.add(MusicRecord(sheet: sheet, achievement: achievement));
          }
        }
        print("[日服客户端] ${diff.$2} 完成，累计：${records.length} 首");
      } catch (e) {
        print("[日服客户端] ${diff.$2} 拉取失败：$e");
      }
    }

    print("[日服客户端] 所有成绩解析完成，总数：${records.length}");
    return records;
  }
}

class MaimaiNETIntlClient extends MaimaiNetClient {
  MaimaiNETIntlClient() {
    urls = URLs.INTL;
    dio.options.headers["Referer"] = URLs.INTL["LOGIN_PAGE"];
    print("[MaimaiNETIntlClient] 国际服客户端初始化");
  }

  @override
  Future<void> login(AuthParams auth) async {
    print("[国际服客户端] 开始登录流程");

    await fetchAsSoup(urls["LOGIN_PAGE"]!);
    print("[国际服客户端] 访问登录页面成功");

    final loginData = {
      "sid": auth.id,
      "password": auth.password,
      "retention": "1",
    };

    final response = await dio.post(
      urls["LOGIN_ENDPOINT"]!,
      data: FormData.fromMap(loginData),
      options: Options(
        contentType: Headers.formUrlEncodedContentType,
      ),
    );

    final location = response.headers.value('location');
    if (location != null) {
      print("[国际服客户端] 登录后跳转：$location");
      await fetchAsSoupWithRedirects(location);
    }
    await fetchAsSoup(urls["HOME"]!);
    print("[国际服客户端] 登录流程完成");
  }

  @override
  Future<UserInfo> fetchUserInfo() async {
    print("[国际服客户端] 获取用户信息");
    final soup = await fetchAsSoup(urls["HOME"]!);
    final avatarImg = soup.querySelector('img[loading="lazy"].w_112');
    final avatar = avatarImg?.attributes['src'] ?? '';
    final nameEl = soup.querySelector(".name_block");
    final name = nameEl?.text.trim() ?? '';

    String trophyClass = "Normal";
    String trophyName = "";
    final trophyEl = soup.querySelector("div.trophy_block");
    if (trophyEl != null) {
      for (final c in trophyEl.classes) {
        if (["trophy_Normal", "trophy_Silver", "trophy_Gold", "trophy_Rainbow"].contains(c)) {
          trophyClass = c.replaceFirst('trophy_', '');
          break;
        }
      }
      final trophyInner = trophyEl.querySelector(".trophy_inner_block span");
      if (trophyInner != null) trophyName = trophyInner.text.trim();
    }

    final courseImg = soup.querySelector('img[src*="course/course_rank_"]');
    final courseIcon = courseImg?.attributes['src'] ?? '';
    final classImg = soup.querySelector('img[src*="class/class_rank_"]');
    final classIcon = classImg?.attributes['src'] ?? '';

    String plateUrl = "";
    try {
      print("[国际服客户端] 获取铭牌");
      final plateSoup = await fetchAsSoup(urls["NAMEPLATE"]!);
      final plateImg = plateSoup.querySelector('img[loading="lazy"].w_396.m_r_10');
      if (plateImg != null) plateUrl = plateImg.attributes['src'] ?? '';
    } catch (e) {
      print("[国际服客户端] 获取铭牌失败：$e");
    }

    print("[国际服客户端] 用户信息解析完成：$name");
    return UserInfo(
      avatar: avatar,
      name: name,
      trophy: {'rarity': trophyClass, 'text': trophyName},
      courseIcon: courseIcon,
      classIcon: classIcon,
      plateUrl: plateUrl,
    );
  }

  @override
  Future<List<MusicRecord>> fetchMusicRecords({OnProgress? onProgress}) async {
    print("[国际服客户端] 开始获取所有歌曲成绩");
    final difficulties = [
      ("0", "Basic"),
      ("1", "Advanced"),
      ("2", "Expert"),
      ("3", "Master"),
      ("4", "Remaster"),
      ("10", "Utage"),
    ];

    int total = 0;
    int current = 0;

    final futures = difficulties.map((diff) async {
      onProgress?.call("正在并发拉取难度..", null);
      final url = urls["RECORD_MUSICS_PAGE"]!;
      final soup = await fetchAsSoup(url, queryParameters: {"genre": "99", "diff": diff.$1});
      final items = soup.querySelectorAll(".w_450.m_15.p_r.f_0");
      final list = <MusicRecord>[];

      for (final item in items) {
        final sheet = parseSheetInfo(item);
        if (sheet != null) {
          final achievement = parseAchievement(item);
          list.add(MusicRecord(sheet: sheet, achievement: achievement));
        }
        current++;
      }
      return list;
    });

    final results = await Future.wait(futures);
    final all = results.expand((e) => e).toList();
    print("[国际服客户端] 所有成绩解析完成，总数：${all.length}");
    return all;
  }
}

Future<Map<String, dynamic>> fetchNetRecords(Region region, AuthParams auth) async {
  print("[fetchNetRecords] 外部接口调用，区域：${region.name}");
  final MaimaiNetClient client;
  if (region == Region.jp) {
    client = MaimaiNETJpClient();
  } else {
    client = MaimaiNETIntlClient();
  }

  await client.login(auth);
  final userInfo = await client.fetchUserInfo();
  final musicRecords = await client.fetchMusicRecords();

  return {
    "user": userInfo.toJson(),
    "music": musicRecords.map((e) => e.toJson()).toList(),
    "recent": [],
  };
}