// ===========================================================================
// FILE: lib/logic/game_controller.dart
// ===========================================================================

import 'dart:async';
import 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import '../models/game_board.dart';
export '../models/game_board.dart';

import 'ai_engine.dart';
import '../services/audio_service.dart';
import '../services/storage_service.dart';
import '../services/multiplayer_service.dart';
import '../core/app_colors.dart';

class GameController extends ChangeNotifier {
  late GameBoard board;
  bool isVsCPU = false;
  bool isCPUThinking = false;

  bool isOnline = false;
  String? roomCode;
  bool isHost = false;
  StreamSubscription<DocumentSnapshot>? _onlineSubscription;

  bool opponentLeft = false;
  bool _hasSavedResult = false;

  Timer? _blitzTimer;
  int timeLeft = 15;
  final int maxTime = 15;
  bool isTimeMode = true;

  String effectText = '';
  Color effectColor = Colors.transparent;
  Timer? _effectTimer;

  String? myReaction;
  String? opponentReaction;
  Timer? _myReactionTimer;
  Timer? _oppReactionTimer;

  Timestamp? _lastOpponentReactionTime;

  bool rematchRequested = false;
  bool opponentWantsRematch = false;
  int lastMatchXP = 0;

  bool isSetupPhase = true;
  bool myJokerPlaced = false;
  bool oppJokerPlaced = false;
  Player jokerTurn = Player.red;

  Player get myPlayer => isOnline ? (isHost ? Player.red : Player.blue) : Player.red;
  bool get isGameOver => board.isGameOver;

  int cpuLevel = 1;
  int currentMatchLevel = 1;
  int? currentSeed;
  AppThemeType _activeTheme = AppThemeType.cyberpunk;

  String onlineHostName = "ROSSO";
  String onlineGuestName = "BLU";
  ArenaShape onlineShape = ArenaShape.classic;

  GameController({int radius = 3}) {
    cpuLevel = StorageService.instance.cpuLevel;
    startNewGame(radius);
  }

  void startNewGame(int radius, {bool vsCPU = false, bool isOnline = false, String? roomCode, bool isHost = false, ArenaShape shape = ArenaShape.classic, bool timeMode = true}) {
    _onlineSubscription?.cancel();
    _onlineSubscription = null;
    _blitzTimer?.cancel();
    _effectTimer?.cancel();
    effectText = '';
    _hasSavedResult = false;
    lastMatchXP = 0;

    myReaction = null;
    opponentReaction = null;
    _lastOpponentReactionTime = null;
    rematchRequested = false;
    opponentWantsRematch = false;

    isSetupPhase = true;
    myJokerPlaced = false;
    oppJokerPlaced = false;
    jokerTurn = Player.red;

    this.isVsCPU = vsCPU;
    this.isOnline = isOnline;
    this.roomCode = roomCode;
    this.isHost = isHost;
    this.isTimeMode = timeMode;

    onlineShape = shape;
    int levelToUse = isOnline ? (currentMatchLevel == 1 ? 2 : currentMatchLevel) : cpuLevel;

    board = GameBoard(radius: radius, level: levelToUse, seed: currentSeed, shape: onlineShape);
    board.currentPlayer = Player.red;

    isCPUThinking = false;
    opponentLeft = false;

    if (this.isOnline && this.roomCode != null) {
      _listenToOnlineGame(this.roomCode!);
    }

    notifyListeners();
  }

  void placeJoker(int bx, int by) {
    if (!isSetupPhase) return;

    Box? target;
    try { target = board.boxes.firstWhere((b) => b.x == bx && b.y == by); } catch(e) {}

    if (target == null || target.type == BoxType.invisible || target.hiddenJokerOwner != null) return;

    AudioService.instance.playLineSfx(_activeTheme);

    if (isOnline) {
      if (myJokerPlaced) return;
      target.hiddenJokerOwner = myPlayer;
      myJokerPlaced = true;

      String prefix = isHost ? 'p1' : 'p2';
      FirebaseFirestore.instance.collection('games').doc(roomCode).update({
        '${prefix}_joker': {'x': bx, 'y': by}
      });
    } else {
      target.hiddenJokerOwner = jokerTurn;
      if (jokerTurn == Player.red) {
        jokerTurn = Player.blue;
        if (isVsCPU) {
          _placeCpuJoker();
        }
      } else {
        jokerTurn = Player.red;
      }
    }
    notifyListeners();
    _checkSetupComplete();
  }

  void _placeCpuJoker() {
    var validBoxes = board.boxes.where((b) => b.type != BoxType.invisible && b.hiddenJokerOwner == null).toList();
    if (validBoxes.isNotEmpty) {
      var b = validBoxes[Random().nextInt(validBoxes.length)];
      b.hiddenJokerOwner = Player.blue;
    }
    jokerTurn = Player.red;
    _checkSetupComplete();
  }

  void _checkSetupComplete() {
    if (isOnline) {
      if (myJokerPlaced && oppJokerPlaced) {
        isSetupPhase = false;
        _startTimer();
      }
    } else {
      if (jokerTurn == Player.red) {
        isSetupPhase = false;
        _startTimer();
      }
    }
    notifyListeners();
  }

  void sendReaction(String reaction) {
    if (!isOnline || roomCode == null) return;
    MultiplayerService().sendReaction(roomCode!, isHost, reaction);
    _showReaction(true, reaction);
  }

  void requestRematch() {
    if (!isOnline || roomCode == null) return;
    rematchRequested = true;
    notifyListeners();
    MultiplayerService().requestRematch(roomCode!, isHost);
  }

  void _showReaction(bool isMe, String reaction) {
    if (isMe) {
      myReaction = reaction;
      _myReactionTimer?.cancel();
      _myReactionTimer = Timer(const Duration(seconds: 4), () { myReaction = null; notifyListeners(); });
    } else {
      opponentReaction = reaction;
      _oppReactionTimer?.cancel();
      _oppReactionTimer = Timer(const Duration(seconds: 4), () { opponentReaction = null; notifyListeners(); });
    }
    notifyListeners();
  }

  void triggerSpecialEffect(String text, Color color) {
    effectText = text;
    effectColor = color;
    notifyListeners();
    _effectTimer?.cancel();
    _effectTimer = Timer(const Duration(milliseconds: 1200), () { effectText = ''; notifyListeners(); });
  }

  void _playEffects(List<Box> newClosed, {List<Box> newGhosts = const [], required bool isOpponent}) {
    if (newGhosts.isNotEmpty) {
      AudioService.instance.playBombSfx();
      triggerSpecialEffect("TRAPPOLA!", Colors.grey.shade400);
      HapticFeedback.heavyImpact();
      return;
    }

    bool isIceCracked = board.lastMove?.isIceCracked ?? false;
    if (isIceCracked) {
      AudioService.instance.playLineSfx(_activeTheme);
      triggerSpecialEffect("GHIACCIO INCRINATO!", Colors.cyanAccent);
      HapticFeedback.mediumImpact();
      return;
    }

    if (newClosed.isEmpty) {
      AudioService.instance.playLineSfx(_activeTheme);
      if (!isOpponent) HapticFeedback.lightImpact();
    } else {
      for (var b in newClosed) {
        if (b.isJokerRevealed) {
          if (b.owner == b.hiddenJokerOwner) {
            AudioService.instance.playBonusSfx();
            triggerSpecialEffect("JOLLY! +2", Colors.greenAccent);
          } else {
            AudioService.instance.playBombSfx();
            triggerSpecialEffect("JOLLY! -1", Colors.redAccent);
          }
          HapticFeedback.heavyImpact();
          return;
        }
      }

      bool isGold = newClosed.any((b) => b.type == BoxType.gold);
      bool isBomb = newClosed.any((b) => b.type == BoxType.bomb);
      bool isSwap = newClosed.any((b) => b.type == BoxType.swap);
      bool isMultiplier = newClosed.any((b) => b.type == BoxType.multiplier);

      if (isSwap) {
        AudioService.instance.playBonusSfx();
        triggerSpecialEffect("SCAMBIO!", Colors.purpleAccent);
        HapticFeedback.heavyImpact();
      } else if (isMultiplier) {
        AudioService.instance.playBonusSfx();
        triggerSpecialEffect("MOLTIPLICATORE x2!", Colors.yellowAccent);
        HapticFeedback.heavyImpact();
      } else if (isGold) {
        AudioService.instance.playBonusSfx(); triggerSpecialEffect("+2", Colors.amber); HapticFeedback.heavyImpact();
      } else if (isBomb) {
        AudioService.instance.playBombSfx(); triggerSpecialEffect("-1", Colors.redAccent); HapticFeedback.heavyImpact();
      } else {
        AudioService.instance.playBoxSfx(_activeTheme); HapticFeedback.heavyImpact();
      }
    }
  }

  void _startTimer() {
    _blitzTimer?.cancel();
    if (isSetupPhase) return;

    timeLeft = maxTime;
    if (!isTimeMode) { notifyListeners(); return; }

    _blitzTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (isGameOver || isCPUThinking) { timer.cancel(); return; }
      if (timeLeft > 0) {
        timeLeft--; notifyListeners();
      } else {
        timer.cancel();
        if (!isOnline || board.currentPlayer == myPlayer) { _handleTimeOut(); }
      }
    });
  }

  void _handleTimeOut() {
    if (!isTimeMode || isSetupPhase) return;

    if (isOnline) {
      Line randomMove = AIEngine.getBestMove(board, 5);
      handleLineTap(randomMove, _activeTheme, forced: true);
    } else if (isVsCPU && board.currentPlayer == Player.red) {
      Line randomMove = AIEngine.getBestMove(board, cpuLevel);
      handleLineTap(randomMove, _activeTheme, forced: true);
    } else if (!isVsCPU) {
      Line randomMove = AIEngine.getBestMove(board, 5);
      handleLineTap(randomMove, _activeTheme, forced: true);
    }
  }

  void disconnectOnlineGame() {
    _onlineSubscription?.cancel();
    _onlineSubscription = null;
    _blitzTimer?.cancel();
    _effectTimer?.cancel();
    _myReactionTimer?.cancel();
    _oppReactionTimer?.cancel();
    _lastOpponentReactionTime = null;

    if (isOnline && roomCode != null) {
      FirebaseFirestore.instance.collection('games').doc(roomCode).update({'status': 'abandoned'}).catchError((e) => null);
    }
    isOnline = false; roomCode = null; currentMatchLevel = 1; currentSeed = null;
  }

  @override
  void dispose() { disconnectOnlineGame(); super.dispose(); }

  void _listenToOnlineGame(String code) {
    _onlineSubscription = FirebaseFirestore.instance.collection('games').doc(code).snapshots().listen((doc) {
      if (!doc.exists) return;
      var data = doc.data() as Map<String, dynamic>;

      onlineHostName = data['hostName'] ?? "ROSSO";
      onlineGuestName = (data['guestName'] != null && data['guestName'] != '') ? data['guestName'] : "BLU";

      if (data['status'] == 'abandoned' && !board.isGameOver && !opponentLeft) {
        opponentLeft = true; notifyListeners(); return;
      }

      String? p1React = data['p1_reaction'];
      Timestamp? p1Time = data['p1_reaction_time'] as Timestamp?;
      String? p2React = data['p2_reaction'];
      Timestamp? p2Time = data['p2_reaction_time'] as Timestamp?;

      if (isHost && p2React != null && p2Time != null && p2Time != _lastOpponentReactionTime) {
        _lastOpponentReactionTime = p2Time;
        _showReaction(false, p2React);
      } else if (!isHost && p1React != null && p1Time != null && p1Time != _lastOpponentReactionTime) {
        _lastOpponentReactionTime = p1Time;
        _showReaction(false, p1React);
      }

      bool p1Rematch = data['p1_rematch'] ?? false;
      bool p2Rematch = data['p2_rematch'] ?? false;
      opponentWantsRematch = isHost ? p2Rematch : p1Rematch;

      if (data['status'] == 'playing' && (data['moves'] as List).isEmpty && rematchRequested) {
        currentSeed = data['seed'];
        startNewGame(data['radius'], isOnline: true, roomCode: roomCode, isHost: isHost, shape: ArenaShape.values.firstWhere((e) => e.name == data['shape']), timeMode: data['timeMode']);
        return;
      }

      if (p1Rematch && p2Rematch && isHost && data['status'] != 'playing') {
        currentMatchLevel++;
        int newSeed = DateTime.now().millisecondsSinceEpoch % 1000000;
        final rand = Random();
        int newRadius = rand.nextInt(4) + 3;
        ArenaShape newShape = ArenaShape.values[rand.nextInt(ArenaShape.values.length)];
        MultiplayerService().resetMatch(roomCode!, newRadius, newShape.name, newSeed);
      }

      if (isSetupPhase) {
        if (!isHost && data['p1_joker'] != null && !oppJokerPlaced) {
          int jx = data['p1_joker']['x']; int jy = data['p1_joker']['y'];
          board.boxes.firstWhere((b) => b.x == jx && b.y == jy).hiddenJokerOwner = Player.red;
          oppJokerPlaced = true; _checkSetupComplete();
        }
        if (isHost && data['p2_joker'] != null && !oppJokerPlaced) {
          int jx = data['p2_joker']['x']; int jy = data['p2_joker']['y'];
          board.boxes.firstWhere((b) => b.x == jx && b.y == jy).hiddenJokerOwner = Player.blue;
          oppJokerPlaced = true; _checkSetupComplete();
        }
      }

      List<dynamic> moves = data['moves'] ?? [];
      int hostLevel = data['matchLevel'] ?? 1;
      int? hostSeed = data['seed'];
      int hostRadius = data['radius'] ?? board.radius;
      String shapeStr = data['shape'] ?? 'classic';
      ArenaShape hostShape = ArenaShape.values.firstWhere((e) => e.name == shapeStr, orElse: () => ArenaShape.classic);
      onlineShape = hostShape;
      isTimeMode = data['timeMode'] ?? true;

      if (!rematchRequested && (hostLevel > currentMatchLevel || (isOnline && currentSeed == null && hostSeed != null) || (hostSeed != null && hostSeed != currentSeed))) {
        currentMatchLevel = hostLevel; currentSeed = hostSeed;
        int levelToUse = (currentMatchLevel == 1) ? 2 : currentMatchLevel;
        board = GameBoard(radius: hostRadius, level: levelToUse, seed: currentSeed, shape: onlineShape);
        board.currentPlayer = Player.red;
        isCPUThinking = false; notifyListeners(); return;
      }

      int firebaseMovesCount = moves.length;
      int localMovesCount = board.lines.where((l) => l.owner != Player.none).length;

      if (firebaseMovesCount == 0 && localMovesCount > 0 && !rematchRequested) {
        int levelToUse = (currentMatchLevel == 1) ? 2 : currentMatchLevel;
        board = GameBoard(radius: hostRadius, level: levelToUse, seed: currentSeed, shape: onlineShape);
        board.currentPlayer = Player.red;
        notifyListeners(); return;
      }

      if (firebaseMovesCount > localMovesCount) {
        bool newMovesApplied = false;

        for (int i = localMovesCount; i < firebaseMovesCount; i++) {
          var m = moves[i]; Line? lineToPlay;
          for (var line in board.lines) {
            if ((line.p1.x == m['x1'] && line.p1.y == m['y1'] && line.p2.x == m['x2'] && line.p2.y == m['y2']) ||
                (line.p1.x == m['x2'] && line.p1.y == m['y2'] && line.p2.x == m['x1'] && line.p2.y == m['y1'])) {
              lineToPlay = line; break;
            }
          }
          if (lineToPlay != null && lineToPlay.owner == Player.none) {
            Player playerFromFirebase = (m['player'] == 'red') ? Player.red : Player.blue;
            bool isOpponentMove = (playerFromFirebase != myPlayer);
            List<Box> closedBefore = board.boxes.where((b) => b.owner != Player.none).toList();
            List<Box> ghostsBefore = board.boxes.where((b) => b.type == BoxType.invisible && b.isRevealed).toList();

            board.playMove(lineToPlay, forcedPlayer: playerFromFirebase);
            newMovesApplied = true;

            List<Box> newClosed = board.boxes.where((b) => b.owner != Player.none && !closedBefore.contains(b)).toList();
            List<Box> newGhosts = board.boxes.where((b) => b.type == BoxType.invisible && b.isRevealed && !ghostsBefore.contains(b)).toList();

            if (isOpponentMove) _playEffects(newClosed, newGhosts: newGhosts, isOpponent: true);
          }
        }

        if (newMovesApplied) {
          String expectedTurnStr = data['turn'] ?? 'red';
          Player expectedTurn = expectedTurnStr == 'red' ? Player.red : Player.blue;
          if (!board.isGameOver && board.currentPlayer != expectedTurn) { board.currentPlayer = expectedTurn; }
          _startTimer();
        }

        if (board.isGameOver) _saveMatchResult();
        notifyListeners();
      }
    });
  }

  void handleLineTap(Line line, AppThemeType theme, {bool forced = false}) {
    if ((isSetupPhase || isCPUThinking || board.isGameOver || opponentLeft) && !forced) return;
    if (isOnline && board.currentPlayer != myPlayer && !forced) return;

    _activeTheme = theme;
    List<Box> closedBefore = board.boxes.where((b) => b.owner != Player.none).toList();
    List<Box> ghostsBefore = board.boxes.where((b) => b.type == BoxType.invisible && b.isRevealed).toList();

    if (board.playMove(line)) {
      List<Box> newClosed = board.boxes.where((b) => b.owner != Player.none && !closedBefore.contains(b)).toList();
      List<Box> newGhosts = board.boxes.where((b) => b.type == BoxType.invisible && b.isRevealed && !ghostsBefore.contains(b)).toList();

      if (!forced) _playEffects(newClosed, newGhosts: newGhosts, isOpponent: false);

      _startTimer(); notifyListeners();

      if (isOnline && roomCode != null) {
        Map<String, dynamic> moveData = {
          'x1': line.p1.x, 'y1': line.p1.y, 'x2': line.p2.x, 'y2': line.p2.y,
          'player': myPlayer == Player.red ? 'red' : 'blue'
        };
        String nextTurnStr = board.currentPlayer == Player.red ? 'red' : 'blue';

        FirebaseFirestore.instance.collection('games').doc(roomCode).update({
          'moves': FieldValue.arrayUnion([moveData]),
          'turn': nextTurnStr
        }).catchError((e) => debugPrint("Errore: $e"));

        if (board.isGameOver) {
          _saveMatchResult();
          if (isHost) FirebaseFirestore.instance.collection('games').doc(roomCode).update({'status': 'finished'});
        }
      } else {
        if (board.isGameOver) _saveMatchResult();
        else if (isVsCPU && board.currentPlayer == Player.blue) _checkCPUTurn();
      }
    }
  }

  void _checkCPUTurn() async {
    if (isVsCPU && board.currentPlayer == Player.blue && !board.isGameOver) {
      isCPUThinking = true; _blitzTimer?.cancel(); notifyListeners();
      await Future.delayed(const Duration(milliseconds: 600));

      if (!board.isGameOver) {
        List<Box> closedBefore = board.boxes.where((b) => b.owner != Player.none).toList();
        List<Box> ghostsBefore = board.boxes.where((b) => b.type == BoxType.invisible && b.isRevealed).toList();

        Line bestMove = AIEngine.getBestMove(board, cpuLevel);
        board.playMove(bestMove);

        List<Box> newClosed = board.boxes.where((b) => b.owner != Player.none && !closedBefore.contains(b)).toList();
        List<Box> newGhosts = board.boxes.where((b) => b.type == BoxType.invisible && b.isRevealed && !ghostsBefore.contains(b)).toList();

        _playEffects(newClosed, newGhosts: newGhosts, isOpponent: true);

        isCPUThinking = false; _startTimer(); notifyListeners();

        if (board.isGameOver) _saveMatchResult();
        else _checkCPUTurn();
      }
    }
  }

  void _saveMatchResult() {
    if (_hasSavedResult) return;
    _hasSavedResult = true;

    int calculatedXP = 0;
    bool isDraw = board.scoreRed == board.scoreBlue;
    String myRealName = StorageService.instance.playerName;
    if (myRealName.isEmpty) myRealName = "IO";

    if (isOnline) {
      bool isWin = isHost ? board.scoreRed > board.scoreBlue : board.scoreBlue > board.scoreRed;
      calculatedXP = isWin ? 20 : (isDraw ? 5 : 2);
      String oppName = isHost ? onlineGuestName : onlineHostName;
      int myScore = isHost ? board.scoreRed : board.scoreBlue;
      int oppScore = isHost ? board.scoreBlue : board.scoreRed;
      StorageService.instance.saveMatchToHistory(myName: myRealName, opponent: oppName, myScore: myScore, oppScore: oppScore, isOnline: true);

      if (isWin) StorageService.instance.updateQuestProgress(0, 1); // Missione: Vinci Online

    } else if (isVsCPU) {
      int myScore = board.scoreRed; int cpuScore = board.scoreBlue;
      bool isWin = myScore > cpuScore;
      calculatedXP = isWin ? (10 + (cpuLevel * 2)) : (isDraw ? 5 : 2);

      if (isWin) {
        StorageService.instance.addWin();
        StorageService.instance.updateQuestProgress(1, 1); // Missione: Vinci vs CPU
      } else if (cpuScore > myScore) {
        StorageService.instance.addLoss();
      }
      StorageService.instance.saveMatchToHistory(myName: myRealName, opponent: "CPU (Liv. $cpuLevel)", myScore: myScore, oppScore: cpuScore, isOnline: false);
    } else {
      calculatedXP = 2;
      StorageService.instance.saveMatchToHistory(myName: myRealName, opponent: "Ospite (Locale)", myScore: board.scoreRed, oppScore: board.scoreBlue, isOnline: false);
    }

    // Se si sta giocando in una forma speciale (non classica)
    if (board.shape != ArenaShape.classic) {
      StorageService.instance.updateQuestProgress(2, 1); // Missione: Usa forme speciali
    }

    lastMatchXP = calculatedXP; StorageService.instance.addXP(calculatedXP); notifyListeners();
  }

  void increaseLevelAndRestart() {
    cpuLevel++; StorageService.instance.saveCpuLevel(cpuLevel);
    startNewGame(board.radius, vsCPU: true, shape: board.shape, timeMode: isTimeMode);
  }
}