import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/clock_in_repository_interface.dart';

/// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect.
class ClockInRepositoryImpl implements ClockInRepositoryInterface {
  final dc.ExampleConnector _dataConnect;
  final firebase.FirebaseAuth _firebaseAuth;

  ClockInRepositoryImpl({
    required dc.ExampleConnector dataConnect,
    required firebase.FirebaseAuth firebaseAuth,
  })  : _dataConnect = dataConnect,
        _firebaseAuth = firebaseAuth;

  Future<String> _getStaffId() async {
    final firebase.User? user = _firebaseAuth.currentUser;
    if (user == null) throw Exception('User not authenticated');

    final QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> result =
        await _dataConnect.getStaffByUserId(userId: user.uid).execute();
    if (result.data.staffs.isEmpty) {
      throw Exception('Staff profile not found');
    }
    return result.data.staffs.first.id;
  }

  /// Helper to convert Data Connect Timestamp to DateTime
  DateTime? _toDateTime(dynamic t) {
    if (t == null) return null;
    // Attempt to use toJson assuming it matches the generated code's expectation of String
    try {
      // If t has toDate (e.g. cloud_firestore), usage would be t.toDate()
      // But here we rely on toJson or toString
      return DateTime.tryParse(t.toJson() as String);
    } catch (_) {
      try {
         return DateTime.tryParse(t.toString());
      } catch (e) {
         return null;
      }
    }
  }
  
  /// Helper to create Timestamp from DateTime
  Timestamp _fromDateTime(DateTime d) {
    // Assuming Timestamp.fromJson takes an ISO string
    return Timestamp.fromJson(d.toIso8601String());
  }

  /// Helper to find today's active application
  Future<dc.GetApplicationsByStaffIdApplications?> _getTodaysApplication(String staffId) async {
    final DateTime now = DateTime.now();
    
    // Fetch recent applications (assuming meaningful limit)
    final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> result = 
      await _dataConnect.getApplicationsByStaffId(
        staffId: staffId,
    ).limit(20).execute();

    try {
      return result.data.applications.firstWhere((dc.GetApplicationsByStaffIdApplications app) {
        final DateTime? shiftTime = _toDateTime(app.shift.startTime); 
        
        if (shiftTime == null) return false;

        final bool isSameDay = shiftTime.year == now.year && 
                          shiftTime.month == now.month && 
                          shiftTime.day == now.day;
        
        if (!isSameDay) return false;

        // Check Status
        final dynamic status = app.status.stringValue;
        return status != 'PENDING' && status != 'REJECTED' && status != 'NO_SHOW' && status != 'CANCELED';
      });
    } catch (e) {
      return null;
    }
  }

  @override
  Future<Shift?> getTodaysShift() async {
    final String staffId = await _getStaffId();
    final dc.GetApplicationsByStaffIdApplications? app = await _getTodaysApplication(staffId);

    if (app == null) return null;

    final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift; 
    
    final QueryResult<dc.GetShiftByIdData, dc.GetShiftByIdVariables> shiftResult = 
        await _dataConnect.getShiftById(id: shift.id).execute();
    
    if (shiftResult.data.shift == null) return null;
    
    final dc.GetShiftByIdShift fullShift = shiftResult.data.shift!;
    
    return Shift(
      id: fullShift.id,
      title: fullShift.title,
      clientName: fullShift.order.business.businessName, 
      logoUrl: '', // Not available in GetShiftById
      hourlyRate: 0.0, 
      location: fullShift.location ?? '',
      locationAddress: fullShift.locationAddress ?? '',
      date: _toDateTime(fullShift.startTime)?.toIso8601String() ?? '',
      startTime: _toDateTime(fullShift.startTime)?.toIso8601String() ?? '',
      endTime: _toDateTime(fullShift.endTime)?.toIso8601String() ?? '',
      createdDate: _toDateTime(fullShift.createdAt)?.toIso8601String() ?? '',
      status: fullShift.status?.stringValue,
      description: fullShift.description,
    ); 
  }

  @override
  Future<AttendanceStatus> getAttendanceStatus() async {
    final String staffId = await _getStaffId();
    final dc.GetApplicationsByStaffIdApplications? app = await _getTodaysApplication(staffId);

    if (app == null) {
      return const AttendanceStatus(isCheckedIn: false);
    }

    return ClockInAdapter.toAttendanceStatus(
      status: app.status.stringValue,
      checkInTime: _toDateTime(app.checkInTime),
      checkOutTime: _toDateTime(app.checkOutTime),
      activeShiftId: app.shiftId,
    );
  }

  @override
  Future<AttendanceStatus> clockIn({required String shiftId, String? notes}) async {
    final String staffId = await _getStaffId();
    
    final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> appsResult = 
        await _dataConnect.getApplicationsByStaffId(staffId: staffId).execute();
    
    final dc.GetApplicationsByStaffIdApplications app = appsResult.data.applications.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId);
    
    await _dataConnect.updateApplicationStatus(
      id: app.id,
      roleId: app.shiftRole.id, 
    )
    .status(dc.ApplicationStatus.CHECKED_IN)
    .checkInTime(_fromDateTime(DateTime.now()))
    .execute();

    return getAttendanceStatus();
  }

  @override
  Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}) async {
    final String staffId = await _getStaffId();
    
    final dc.GetApplicationsByStaffIdApplications? app = await _getTodaysApplication(staffId);
    if (app == null) throw Exception('No active shift found to clock out');

    await _dataConnect.updateApplicationStatus(
       id: app.id,
       roleId: app.shiftRole.id,
    )
    .status(dc.ApplicationStatus.CHECKED_OUT)
    .checkOutTime(_fromDateTime(DateTime.now()))
    .execute();

    return getAttendanceStatus();
  }

  @override
  Future<List<Map<String, dynamic>>> getActivityLog() async {
    // Placeholder as this wasn't main focus and returns raw maps
    return <Map<String, dynamic>>[];
  }
}
