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

import 'package:http/http.dart' as http;

import '../../../../core/config/app_config.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/logging/app_logger.dart';
import '../models/task_model.dart';

abstract class TaskRemoteDataSource {
  Future<List<TaskModel>> getTasksByDate(String date, {String? status});
  Future<TaskModel> getTaskById(String id);
  Future<TaskModel> createTask(TaskModel task);
  Future<TaskModel> updateTask(TaskModel task);
  Future<void> deleteTask(String id);
  Future<TaskModel> toggleTaskStatus(String id);
  Future<TaskModel> rescheduleTask(String id, String targetDate);
}

class TaskRemoteDataSourceImpl implements TaskRemoteDataSource {
  final AppLogger logger;
  final http.Client _client;

  String get _baseUrl => AppConfig.current.apiBaseUrl;

  TaskRemoteDataSourceImpl({
    required this.logger,
    required http.Client client,
  }) : _client = client;

  Map<String, String> get _headers => {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      };

  @override
  Future<List<TaskModel>> getTasksByDate(String date, {String? status}) async {
    final queryParams = {'date': date};
    if (status != null && status != 'all') {
      queryParams['status'] = status;
    }

    final uri = Uri.parse('$_baseUrl/tasks').replace(queryParameters: queryParams);
    logger.info('GET $uri');

    try {
      final response = await _client.get(uri, headers: _headers);
      logger.debug('Response: ${response.statusCode}');

      if (response.statusCode == 200) {
        final List<dynamic> jsonList = json.decode(response.body);
        return jsonList.map((json) => TaskModel.fromJson(json)).toList();
      } else {
        throw ServerException(
          message: 'Failed to load tasks',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw const NetworkException();
    }
  }

  @override
  Future<TaskModel> getTaskById(String id) async {
    final uri = Uri.parse('$_baseUrl/tasks/$id');
    logger.info('GET $uri');

    try {
      final response = await _client.get(uri, headers: _headers);

      if (response.statusCode == 200) {
        return TaskModel.fromJson(json.decode(response.body));
      } else if (response.statusCode == 404) {
        throw const NotFoundException(message: 'Task not found');
      } else {
        throw ServerException(
          message: 'Failed to load task',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw const NetworkException();
    }
  }

  @override
  Future<TaskModel> createTask(TaskModel task) async {
    final uri = Uri.parse('$_baseUrl/tasks');
    logger.info('POST $uri');

    try {
      final response = await _client.post(
        uri,
        headers: _headers,
        body: json.encode(task.toCreateJson()),
      );
      logger.debug('Response: ${response.statusCode}');

      if (response.statusCode == 200 || response.statusCode == 201) {
        return TaskModel.fromJson(json.decode(response.body));
      } else if (response.statusCode == 400) {
        throw ValidationException(
          message: 'Invalid task data',
          errors: json.decode(response.body),
        );
      } else {
        throw ServerException(
          message: 'Failed to create task',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw const NetworkException();
    }
  }

  @override
  Future<TaskModel> updateTask(TaskModel task) async {
    final uri = Uri.parse('$_baseUrl/tasks/${task.id}');
    logger.info('PUT $uri');

    try {
      final response = await _client.put(
        uri,
        headers: _headers,
        body: json.encode(task.toJson()),
      );

      if (response.statusCode == 200) {
        return TaskModel.fromJson(json.decode(response.body));
      } else if (response.statusCode == 404) {
        throw const NotFoundException(message: 'Task not found');
      } else {
        throw ServerException(
          message: 'Failed to update task',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw const NetworkException();
    }
  }

  @override
  Future<void> deleteTask(String id) async {
    final uri = Uri.parse('$_baseUrl/tasks/$id');
    logger.info('DELETE $uri');

    try {
      final response = await _client.delete(uri, headers: _headers);

      if (response.statusCode == 204 || response.statusCode == 200) {
        return;
      } else if (response.statusCode == 404) {
        throw const NotFoundException(message: 'Task not found');
      } else {
        throw ServerException(
          message: 'Failed to delete task',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw const NetworkException();
    }
  }

  @override
  Future<TaskModel> toggleTaskStatus(String id) async {
    final uri = Uri.parse('$_baseUrl/tasks/$id/toggle');
    logger.info('PATCH $uri');

    try {
      final response = await _client.patch(uri, headers: _headers);

      if (response.statusCode == 200) {
        return TaskModel.fromJson(json.decode(response.body));
      } else if (response.statusCode == 404) {
        throw const NotFoundException(message: 'Task not found');
      } else {
        throw ServerException(
          message: 'Failed to toggle task status',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw const NetworkException();
    }
  }

  @override
  Future<TaskModel> rescheduleTask(String id, String targetDate) async {
    final uri = Uri.parse('$_baseUrl/tasks/$id/reschedule');
    logger.info('POST $uri');

    try {
      final response = await _client.post(
        uri,
        headers: _headers,
        body: json.encode({'target_date': targetDate}),
      );

      if (response.statusCode == 200) {
        return TaskModel.fromJson(json.decode(response.body));
      } else if (response.statusCode == 404) {
        throw const NotFoundException(message: 'Task not found');
      } else {
        throw ServerException(
          message: 'Failed to reschedule task',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw const NetworkException();
    }
  }
}
