import 'package:gamer_updater/db.dart';
import 'package:gamer_updater/utils.dart';
import 'package:http/http.dart' as http;
import 'dart:typed_data';

class Game {
  final String name;
  final String versionRegex;
  final RegExp _internalVersionRegex;
  final String lastPlayed;
  String actualVersion;
  String lastUpdated;
  final String rssFeedUrl;
  final Uint8List? imageData;

  Game({
    required this.name,
    required this.versionRegex,
    required this.lastPlayed,
    this.actualVersion = '',
    required this.rssFeedUrl,
    this.lastUpdated = '',
    this.imageData,
  }) : _internalVersionRegex = RegExp(versionRegex);

  factory Game.fromMap(Map<String, dynamic> map) {
    return Game(
      name: map['name'],
      versionRegex: map['version_regex'],
      lastPlayed: map['last_played'],
      rssFeedUrl: map['rss_feed_url'],
      actualVersion: map['actual_version'],
      lastUpdated: map['last_updated'],
      imageData: map['image_data'],
    );
  }

  Future<void> updateActualVersion() async {
    if (rssFeedUrl.isEmpty) {
      throw Exception('No rss feed url for $name');
    }
    final response = await http.get(Uri.parse(rssFeedUrl));
    if (response.statusCode != 200) {
      throw Exception(
        'Failed to update actual version for $name, rss responded with ${response.statusCode}',
      );
    }
    final body = response.body;
    final match = _internalVersionRegex.firstMatch(body);
    if (match == null || match.groupCount == 0) {
      throw Exception('No version found for $name');
    }
    final version = match.group(1);
    if (version == null) {
      throw Exception('No version found for $name');
    }
    actualVersion = version;
    try {
      // Some sites use weird ass dogshit fucking formats
      // We cannot really reliably parse every single one of them
      // So - fuck it
      lastUpdated =
          parseRfc822Date(
            response.headers['last-modified'] ?? '',
          ).toIso8601String();
    } catch (e) {
      lastUpdated = DateTime.now().toIso8601String();
    }
  }
}

class GameRepository {
  static Future<Game> upsert(Game game) async {
    final db = DB.db;
    await db.rawInsert(
      '''
INSERT INTO games
(name, actual_version, last_played, rss_feed_url, version_regex, last_updated)
VALUES
(?, ?, ?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
actual_version = excluded.actual_version,
last_played = excluded.last_played,
rss_feed_url = excluded.rss_feed_url,
version_regex = excluded.version_regex,
last_updated = excluded.last_updated
''',
      [
        game.name,
        game.actualVersion,
        game.lastPlayed,
        game.rssFeedUrl,
        game.versionRegex,
        game.lastUpdated,
      ],
    );
    return game;
  }

  static Future<Game> updateImage(Game game) async {
    final db = DB.db;
    await db.rawUpdate('UPDATE games SET image_data = ? WHERE name = ?', [
      game.imageData,
      game.name,
    ]);
    return game;
  }

  static Future<Map<String, Game>> getAll() async {
    final db = DB.db;
    final games = await db.rawQuery(
      'SELECT name, actual_version, last_played, rss_feed_url, version_regex, last_updated, image_data FROM games ORDER BY name',
    );
    return games
        .map((e) => Game.fromMap(e))
        .fold<Map<String, Game>>({}, (map, game) => {...map, game.name: game});
  }

  static Future<void> delete(Game game) async {
    final db = DB.db;
    await db.rawDelete('DELETE FROM games WHERE name = ?', [game.name]);
  }
}

//CREATE TABLE IF NOT EXISTS games (
//  id INTEGER PRIMARY KEY AUTOINCREMENT,
//  name TEXT NOT NULL,
//  actual_version TEXT NOT NULL,
//  last_played TEXT NOT NULL,
//  rss_feed_url TEXT NOT NULL,
//  version_regex TEXT NOT NULL,
//  last_updated TEXT NOT NULL
//);
//CREATE INDEX IF NOT EXISTS idx_games_name ON games (name);
