import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:drift/drift.dart' hide Column;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:mime/mime.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/daos/object_media_items_dao.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/services/search/google_places_service.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';

class SessionEditor extends StatefulWidget {
  const SessionEditor({super.key, this.data, this.session, this.callback});

  final Session? session;
  final Map<String, dynamic>? data;
  final Function? callback;

  @override
  State<SessionEditor> createState() => _SessionEditorState();
}

// used to pass the result of the found image back to current context...
class SessionPayload {
  String? photoUri;
  String? address;
  List<PlatformFile>? files;
}

class _SessionEditorState extends State<SessionEditor> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  late AppDatabase db;
  String editorType = 'Create';

  final Map<String, TextEditingController> sessionCreateController = {
    'name': TextEditingController(),
    'content': TextEditingController(),
    'status': TextEditingController(),
    'date': TextEditingController(),
    'address': TextEditingController(),
    'media': TextEditingController(),
  };

  final SessionPayload sessionPayload = SessionPayload();

  @override
  void initState() {
    super.initState();
    db = Provider.of<AppDatabase>(context, listen: false);

    // if we're editing a session, we'll want to populate it with the appropriate values
    if (widget.session != null) {
      editorType = 'Edit';
      final Session session = widget.session!;
      sessionCreateController['name']?.text = session.title;
      sessionCreateController['content']?.text = session.content;
      sessionCreateController['status']?.text = session.status.name;
      sessionCreateController['date']?.text =
          DateFormat('yyyy-MM-dd').format(session.date!);
      if (session.address != null) {
        sessionCreateController['address']?.text = session.address!;
      }
    }
  }

  Future createSession(context) async {
    Map<Symbol, Value> payload = {
      Symbol('title'): Value<String>(sessionCreateController['name']!.text),
      Symbol('content'):
          Value<String>(sessionCreateController['content']!.text),
      // we want to maintain existing status during update
      Symbol('status'): widget.session != null
          ? Value<SessionStatus>(widget.session!.status)
          : Value<SessionStatus>(SessionStatus.pending),
      Symbol('date'): Value<DateTime>(
          DateTime.parse(sessionCreateController['date']!.text)),
    };

    // if a session exists we'll want to update it
    // so the payload needs the session id
    if (widget.session != null) {
      payload[Symbol('id')] = Value<int>(widget.session!.id);
    }

    // optional params
    if (sessionCreateController['address']!.text.isNotEmpty) {
      payload[Symbol('address')] =
          Value<String>(sessionCreateController['address']!.text);
    }

    return await SessionsDao(db)
        .createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
  }

  Future deleteSessionMedia(int sessionId, MediaType mediaType) async {
    List<MediaItem> deletedMedia =
        (await MediaItemsDao(db).fromSession(sessionId))
            .where((mediaItem) => mediaItem.type == mediaType)
            .toList();

    for (int i = 0; i < deletedMedia.length; i++) {
      await MediaItemsDao(db).remove(deletedMedia[i]);
    }
  }

  Future createSessionMedia(
    title,
    sessionId,
    description,
    reference,
    mediaType,
  ) async {
    // if (sessionPayload.photoUri != null) {
    MediaItemsCompanion mediaItem = MediaItemsCompanion(
        title: Value(title),
        description: Value(description),
        reference: Value(reference.toString()),
        type: Value(mediaType));

    return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async {
      ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion(
        objectId: Value(sessionId),
        objectType: Value(ObjectType.sessions),
        mediaId: Value(id),
      );

      await ObjectMediaItemsDao(db).createOrUpdate(omi);
    });
    // }
  }

  @override
  Widget build(BuildContext context) {
    sessionCreateController['date']!.text =
        DateFormat('yyyy-MM-dd').format(DateTime.now());

    return Padding(
        padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
        child: Form(
            key: _formKey,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Padding(
                    padding: EdgeInsets.only(top: 10, bottom: 10),
                    child: Text('$editorType Session',
                        textAlign: TextAlign.center,
                        style: Theme.of(context).textTheme.titleLarge)),
                FormTextInput(
                    controller: sessionCreateController['name']!,
                    title: 'Title'),
                FormTextInput(
                    controller: sessionCreateController['content']!,
                    title: 'Description',
                    icon: Icon(Icons.description_rounded),
                    maxLines: 10),
                FormTextInput(
                    controller: sessionCreateController['date']!,
                    title: 'Date',
                    icon: Icon(Icons.date_range_rounded),
                    onTap: () {
                      showDatePicker(
                              context: context,
                              initialDate: DateTime.now(),
                              firstDate: DateTime.now(),
                              lastDate: DateTime.now().add(Duration(days: 365)))
                          .then((date) {
                        if (date != null) {
                          sessionCreateController['date']?.text =
                              DateFormat('yyyy-MM-dd').format(date);
                        }
                      });
                    }),
                FormSearchInput(
                    title: 'Location (optional)',
                    controller: sessionCreateController['address']!,
                    service: GooglePlacesService(),
                    resultHandler: (content, service) async {
                      if (content.imageReferences != null) {
                        // get a random photo item from the returned result
                        Map<String, dynamic> photo = content.imageReferences![
                            Random().nextInt(content.imageReferences!.length)];

                        await service
                            .fetchPhoto(photo['name'])
                            .then((photoMap) {
                          sessionPayload.photoUri = photoMap['photoUri'];
                        });
                      }

                      sessionPayload.address = content.address;
                      sessionCreateController['address']!.text =
                          content.description;
                      service.finish();
                    }),
                Padding(
                    padding: EdgeInsets.only(top: 10, bottom: 10),
                    child: TextFormField(
                        readOnly: true,
                        decoration: InputDecoration(
                          prefixIcon: Icon(Icons.image_rounded),
                          filled: true,
                          border: OutlineInputBorder(
                            borderSide: BorderSide.none,
                            borderRadius: BorderRadius.circular(12),
                          ),
                          labelText: 'Media (optional)',
                        ),
                        controller: sessionCreateController['media'],
                        onTap: () async {
                          FilePickerResult? result = await FilePicker.platform
                              .pickFiles(
                                  allowMultiple: true, type: FileType.image);

                          if (result != null) {
                            List<PlatformFile> files = result.files;
                            sessionCreateController['media']!.text =
                                files.map((file) => file.name).toString();
                            sessionPayload.files = files;
                          }
                        })),
                Row(mainAxisAlignment: MainAxisAlignment.end, children: [
                  Padding(
                      padding: EdgeInsets.only(top: 10),
                      child: FilledButton(
                          onPressed: () async => {
                                if (_formKey.currentState!.validate())
                                  {
                                    await createSession(_formKey.currentContext)
                                        .then((sessionId) async {
                                      int currentSessionId = sessionId;

                                      // dirft weirdly doesn't return the proper id if
                                      // an upsert ends up bein an update, so we'll
                                      // set the id to the provided session for an update
                                      if (widget.session != null) {
                                        currentSessionId = widget.session!.id;
                                      }

                                      // if we've found a photo add it to media!
                                      if (sessionPayload.photoUri != null) {
                                        await deleteSessionMedia(
                                            currentSessionId,
                                            MediaType.location);
                                        await createSessionMedia(
                                            'Location Image',
                                            currentSessionId,
                                            sessionPayload.address,
                                            sessionPayload.photoUri,
                                            MediaType.location);
                                      }

                                      // if we've selected files to save, save them
                                      if (sessionPayload.files != null) {
                                        for (int i = 0;
                                            i < sessionPayload.files!.length;
                                            i++) {
                                          PlatformFile file =
                                              sessionPayload.files![i];
                                          String? type =
                                              lookupMimeType(file.path!)!
                                                  .split('/')
                                                  .first;
                                          Uint8List fileBytes =
                                              await file.xFile.readAsBytes();

                                          MediaType mediaType =
                                              MediaType.localImage;
                                          if (type == "video") {
                                            mediaType = MediaType.localVideo;
                                          }

                                          await createSessionMedia(
                                              'Local Media',
                                              currentSessionId,
                                              file.name,
                                              base64Encode(fileBytes),
                                              mediaType);
                                        }
                                      }

                                      // if session is null it's new so we show the dialog
                                      // otherwise the dialog is already open, so no need
                                      if (widget.session == null) {
                                        SessionsDao(db)
                                            .find(currentSessionId)
                                            .then((session) =>
                                                showGenericDialog(
                                                    SessionView(
                                                        session: session),
                                                    _formKey.currentContext!));
                                      }

                                      Navigator.pop(
                                          _formKey.currentContext!, 'Submit');

                                      if (widget.callback != null) {
                                        await widget.callback!();
                                      }
                                    })
                                  }
                              },
                          child: Text('Submit')))
                ]),
              ],
            )));
  }
}
