import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'expenses.freezed.dart';
part 'expenses.g.dart';

typedef ExpensesList = List<Expense>;

@freezed
abstract class Expense with _$Expense {
  const Expense._();

  const factory Expense({
    /// Internal id used to identify the expense
    required int id,

    /// Label of the expense
    required String? label,

    /// The cost shall always be a positive value
    required double cost,

    /// Time associated with the expense
    required int time,

    /// Associated file mime type
    required String mimeType,
  }) = _Expense;

  factory Expense.fromJson(Map<String, dynamic> json) =>
      _$ExpenseFromJson(json);

  /// Get associated expense file name
  String get localFileName {
    if (mimeType == "application/pdf") return "$id.pdf";
    if (mimeType == "image/jpeg") return "$id.jpeg";
    if (mimeType == "image/png") return "$id.png";
    return id.toString();
  }
}

@riverpod
Future<ExpensesManager> expenses(Ref ref) async => ExpensesManager.instance();

class ExpensesManager {
  final String storagePath;

  ExpensesManager._({required this.storagePath});

  /// Get an instance of this manager
  static Future<ExpensesManager> instance() async {
    final appDir = await getApplicationDocumentsDirectory();
    final subDir = p.join(appDir.absolute.path, "expenses");
    final result = await Directory(subDir).create(recursive: true);

    return ExpensesManager._(storagePath: result.absolute.path);
  }

  /// Get expenses list file path
  File get expenseFile => File(p.join(storagePath, "list.json"));

  /// Get the files storage path
  Directory get filesStoragePath => Directory(p.join(storagePath, "exp_files"));

  /// Get the current list of expenses
  Future<ExpensesList> getList() async {
    final jsonDec = jsonDecode(await expenseFile.readAsString());
    return List<Expense>.from(jsonDec.map((m) => Expense.fromJson(m)));
  }

  /// Save the list of expenses
  Future<void> saveList(ExpensesList list) async {
    final jsonDoc = jsonEncode(list.map((t) => t.toJson()));
    await expenseFile.writeAsString(jsonDoc);
  }

  /// Add a new expense to the list
  Future<void> add({
    required String? label,
    required double cost,
    required int time,
    required List<int> fileContent,
    required String fileMimeType,
  }) async {
    final list = await getList();

    final exp = Expense(
      id: (list.lastOrNull?.id ?? 0) + Random().nextInt(1000),
      label: label,
      cost: cost,
      time: time,
      mimeType: fileMimeType,
    );

    // Create files storage directory if required
    if (!await filesStoragePath.exists()) {
      await filesStoragePath.create(recursive: true);
    }

    // Save associated file
    final file = File(
      p.join(filesStoragePath.absolute.path, exp.localFileName),
    );
    await file.writeAsBytes(fileContent);

    // Save the list of expenses
    list.add(exp);
    await saveList(list);
  }
}
