import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_data_connect/src/session/staff_session_store.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:intl/intl.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_core/core.dart';
import '../../domain/repositories/shifts_repository_interface.dart';

class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
  final dc.ExampleConnector _dataConnect;
  final FirebaseAuth _auth = FirebaseAuth.instance;

  ShiftsRepositoryImpl() : _dataConnect = dc.ExampleConnector.instance;

  // Cache: ShiftID -> ApplicationID (For Accept/Decline)
  final Map<String, String> _shiftToAppIdMap = {};
  // Cache: ApplicationID -> RoleID (For Accept/Decline w/ Update mutation)
  final Map<String, String> _appToRoleIdMap = {};

  String? _cachedStaffId;

  Future<String> _getStaffId() async {
    // 1. Check Session Store
    final StaffSession? session = StaffSessionStore.instance.session;
    if (session?.staff?.id != null) {
      return session!.staff!.id;
    }

    // 2. Check Cache
    if (_cachedStaffId != null) return _cachedStaffId!;

    // 3. Fetch from Data Connect using Firebase UID
    final user = _auth.currentUser;
    if (user == null) {
      throw Exception('User is not authenticated');
    }

    try {
      final response = await _dataConnect.getStaffByUserId(userId: user.uid).execute();
      if (response.data.staffs.isNotEmpty) {
        _cachedStaffId = response.data.staffs.first.id;
        return _cachedStaffId!;
      }
    } catch (e) {
      // Log or handle error
    }

    // 4. Fallback (should ideally not happen if DB is seeded)
    return user.uid; 
  }
  
  DateTime? _toDateTime(dynamic t) {
    if (t == null) return null;
    DateTime? dt;
    if (t is Timestamp) {
      dt = t.toDateTime();
    } else if (t is String) {
      dt = DateTime.tryParse(t);
    } else {
      try {
        dt = DateTime.tryParse(t.toJson() as String);
      } catch (_) {
        try {
          dt = DateTime.tryParse(t.toString());
        } catch (e) {
          dt = null;
        }
      }
    }

    if (dt != null) {
      return DateTimeUtils.toDeviceTime(dt);
    }
    return null;
  }

  @override
  Future<List<Shift>> getMyShifts() async {
    return _fetchApplications(dc.ApplicationStatus.ACCEPTED);
  }
  
  @override
  Future<List<Shift>> getPendingAssignments() async {
    return _fetchApplications(dc.ApplicationStatus.PENDING);
  }

  @override
  Future<List<Shift>> getCancelledShifts() async {
    return _fetchApplications(dc.ApplicationStatus.REJECTED);
  }

  @override
  Future<List<Shift>> getHistoryShifts() async {
    return _fetchApplications(dc.ApplicationStatus.CHECKED_OUT);
  }

  Future<List<Shift>> _fetchApplications(dc.ApplicationStatus status) async {
    try {
      final staffId = await _getStaffId();
      final response = await _dataConnect
          .getApplicationsByStaffId(staffId: staffId)
          .execute();

      final apps = response.data.applications.where((app) => app.status.stringValue == status.name);
      final List<Shift> shifts = [];

      for (final app in apps) {
          _shiftToAppIdMap[app.shift.id] = app.id;
          _appToRoleIdMap[app.id] = app.shiftRole.id;
          
          final shift = await _getShiftDetails(app.shift.id);
          if (shift != null) {
              // Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED)
              shifts.add(Shift(
                  id: shift.id,
                  title: shift.title,
                  clientName: shift.clientName,
                  logoUrl: shift.logoUrl,
                  hourlyRate: shift.hourlyRate,
                  location: shift.location,
                  locationAddress: shift.locationAddress,
                  date: shift.date,
                  startTime: shift.startTime,
                  endTime: shift.endTime,
                  createdDate: shift.createdDate,
                  status: _mapStatus(status),
                  description: shift.description,
                  durationDays: shift.durationDays,
                  requiredSlots: shift.requiredSlots,
                  filledSlots: shift.filledSlots,
              ));
          }
      }
      return shifts;
    } catch (e) {
      return <Shift>[];
    }
  }

  String _mapStatus(dc.ApplicationStatus status) {
    switch (status) {
      case dc.ApplicationStatus.ACCEPTED:
      case dc.ApplicationStatus.CONFIRMED:
        return 'confirmed';
      case dc.ApplicationStatus.PENDING:
        return 'pending';
      case dc.ApplicationStatus.CHECKED_OUT:
        return 'completed';
      case dc.ApplicationStatus.REJECTED:
        return 'cancelled';
      default:
        return 'open';
    }
  }

  @override
  Future<List<Shift>> getAvailableShifts(String query, String type) async {
      try {
        final result = await _dataConnect.listShifts().execute();
        final allShifts = result.data.shifts;
        
        final List<Shift> mappedShifts = [];
        
        for (final s in allShifts) {
             // For each shift, map to Domain Shift
             // Note: date fields in generated code might be specific types
             final startDt = _toDateTime(s.startTime);
             final endDt = _toDateTime(s.endTime);
             final createdDt = _toDateTime(s.createdAt);
             
             mappedShifts.add(Shift(
                id: s.id,
                title: s.title,
                clientName: s.order.business.businessName,
                logoUrl: null,
                hourlyRate: s.cost ?? 0.0,
                location: s.location ?? '',
                locationAddress: s.locationAddress ?? '',
                date: startDt?.toIso8601String() ?? '',
                startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
                endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
                createdDate: createdDt?.toIso8601String() ?? '',
                status: s.status?.stringValue.toLowerCase() ?? 'open',
                description: s.description,
                durationDays: s.durationDays,
                requiredSlots: null, // Basic list doesn't fetch detailed role stats yet
                filledSlots: null,
             ));
        }

        if (query.isNotEmpty) {
            return mappedShifts.where((s) => 
                s.title.toLowerCase().contains(query.toLowerCase()) || 
                s.clientName.toLowerCase().contains(query.toLowerCase())
            ).toList();
        }
        
        return mappedShifts;

      } catch (e) {
          return <Shift>[];
      }
  }

  @override
  Future<Shift?> getShiftDetails(String shiftId) async {
    return _getShiftDetails(shiftId);
  }
  
  Future<Shift?> _getShiftDetails(String shiftId) async {
      try {
        final result = await _dataConnect.getShiftById(id: shiftId).execute();
        final s = result.data.shift;
        if (s == null) return null;
        
        int? required;
        int? filled;
        try {
            final rolesRes = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
            if (rolesRes.data.shiftRoles.isNotEmpty) {
                 required = 0;
                 filled = 0;
                 for(var r in rolesRes.data.shiftRoles) {
                    required = (required ?? 0) + r.count;
                    filled = (filled ?? 0) + (r.assigned ?? 0);
                 }
            }
        } catch (_) {}

        final startDt = _toDateTime(s.startTime);
        final endDt = _toDateTime(s.endTime);
        final createdDt = _toDateTime(s.createdAt);

        return Shift(
            id: s.id,
            title: s.title,
            clientName: s.order.business.businessName,
            logoUrl: null,
            hourlyRate: s.cost ?? 0.0,
            location: s.location ?? '',
            locationAddress: s.locationAddress ?? '',
            date: startDt?.toIso8601String() ?? '',
            startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
            endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
            createdDate: createdDt?.toIso8601String() ?? '',
            status: s.status?.stringValue ?? 'OPEN',
            description: s.description,
            durationDays: s.durationDays,
            requiredSlots: required,
            filledSlots: filled,
        );
      } catch (e) {
        return null;
      }
  }

  @override
  Future<void> applyForShift(String shiftId, {bool isInstantBook = false}) async {
      final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
      if (rolesResult.data.shiftRoles.isEmpty) throw Exception('No open roles for this shift');
      
      final role = rolesResult.data.shiftRoles.first; 
      
      final staffId = await _getStaffId();
      await _dataConnect.createApplication(
         shiftId: shiftId,
         staffId: staffId,
         roleId: role.roleId,
         status: isInstantBook ? dc.ApplicationStatus.ACCEPTED : dc.ApplicationStatus.PENDING,
         origin: dc.ApplicationOrigin.STAFF,
      ).execute();
  }

  @override
  Future<void> acceptShift(String shiftId) async {
      await _updateApplicationStatus(shiftId, dc.ApplicationStatus.ACCEPTED);
  }
  
  @override
  Future<void> declineShift(String shiftId) async {
       await _updateApplicationStatus(shiftId, dc.ApplicationStatus.REJECTED);
  }

  Future<void> _updateApplicationStatus(String shiftId, dc.ApplicationStatus newStatus) async {
    String? appId = _shiftToAppIdMap[shiftId];
    String? roleId;

    if (appId == null) {
      // Try to find it in pending
      await getPendingAssignments();
    }
    // Re-check map
    appId = _shiftToAppIdMap[shiftId];
    if (appId != null) {
         roleId = _appToRoleIdMap[appId];
    } else {
         // Fallback fetch
         final staffId = await _getStaffId();
         final apps = await _dataConnect.getApplicationsByStaffId(staffId: staffId).execute();
         final app = apps.data.applications.where((a) => a.shiftId == shiftId).firstOrNull;
         if (app != null) {
             appId = app.id;
             roleId = app.shiftRole.id;
         }
    }

    if (appId == null || roleId == null) {
      // If we are rejecting and can't find an application, create one as rejected (declining an available shift)
      if (newStatus == dc.ApplicationStatus.REJECTED) {
          final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
          if (rolesResult.data.shiftRoles.isNotEmpty) {
             final role = rolesResult.data.shiftRoles.first;
             final staffId = await _getStaffId();
             await _dataConnect.createApplication(
                shiftId: shiftId,
                staffId: staffId,
                roleId: role.id,
                status: dc.ApplicationStatus.REJECTED,
                origin: dc.ApplicationOrigin.STAFF,
             ).execute();
             return;
          }
      }
      throw Exception("Application not found for shift $shiftId");
    }

    await _dataConnect.updateApplicationStatus(
      id: appId,
      roleId: roleId,
    )
    .status(newStatus)
    .execute();
  }
}

