package text

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/aarondl/opt/omit"
	"github.com/nyaruka/phonenumbers"
	"github.com/rs/zerolog/log"
	"source.gleipnir.technology/Gleipnir/nidus-sync/config"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db"
	modelcomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/comms/model"
	modelpublicreport "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/publicreport/model"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db/models"
	querycomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/comms"
	querypublicreport "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/publicreport"
	"source.gleipnir.technology/Gleipnir/nidus-sync/lint"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform/background"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform/event"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform/types"
)

func HandleTextMessage(ctx context.Context, source string, destination string, content string) error {
	src, err := ParsePhoneNumber(source)
	if err != nil {
		return fmt.Errorf("parse source '%s': %w", source, err)
	}
	dst, err := ParsePhoneNumber(destination)
	if err != nil {
		return fmt.Errorf("parse destination '%s': %w", destination, err)
	}
	txn, err := db.BeginTxn(ctx)
	if err != nil {
		return fmt.Errorf("start txn: %w", err)
	}
	defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")

	s, err := ensurePhoneInDB(ctx, txn, src)
	if err != nil {
		return fmt.Errorf("ensure source in DB: %w", err)
	}
	is_visible_to_llm := s.ConfirmedMessageID != nil

	l, err := querycomms.TextLogInsert(ctx, txn, modelcomms.TextLog{
		Content:        content,
		Created:        time.Now(),
		Destination:    dst.PhoneString(),
		IsVisibleToLlm: is_visible_to_llm,
		IsWelcome:      false,
		Origin:         modelcomms.Textorigin_Customer,
		Source:         s.E164,
		TwilioSid:      nil,
		TwilioStatus:   "",
	})
	if err != nil {
		return fmt.Errorf("insert text log: %w", err)
	}
	log.Debug().Int32("id", l.ID).Msg("insert comms text log")
	err = background.NewTextRespond(ctx, txn, l.ID)
	if err != nil {
		return fmt.Errorf("text respond: %w", err)
	}
	if err := txn.Commit(ctx); err != nil {
		return fmt.Errorf("commit: %w", err)
	}
	log.Debug().Msg("commit handle text message")
	return err
}

func respondText(ctx context.Context, log_id int32) error {
	txn, err := db.BeginTxn(ctx)
	log.Debug().Msg("respond text txn begin")
	if err != nil {
		return fmt.Errorf("begin tx: %w", err)
	}
	defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")
	l, err := querycomms.TextLogFromID(ctx, txn, int64(log_id))
	if err != nil {
		return fmt.Errorf("find comms %d: %w", log_id, err)
	}
	src, err := ParsePhoneNumber(l.Source)
	if err != nil {
		return fmt.Errorf("parse source: %w", err)
	}

	phone, err := querycomms.PhoneFromE164(ctx, txn, src.PhoneString())
	if err != nil {
		return fmt.Errorf("Failed to get contact phone")
	}

	body_l := strings.TrimSpace(strings.ToLower(l.Content))
	// If the user isn't confirmed for sending regular texts ensure they get a reprompt
	if phone.ConfirmedMessageID == nil {
		switch body_l {
		case "yes":
			err = querycomms.PhoneUpdateConfirmedMessageID(ctx, txn, src.PhoneString(), &l.ID)
			if err != nil {
				return fmt.Errorf("set phone confirmed message ID: %w", err)
			}
			content := "Thanks, we've confirmed your phone number. You can text STOP at any time if you change your mind"
			err = sendTextCommandResponse(ctx, txn, *src, content)
			if err != nil {
				return fmt.Errorf("send response: %w", err)
			}
			lint.LogOnErrCtx(func(ctx context.Context) error { return txn.Commit(ctx) }, ctx, "commit after confirm")
			lint.LogOnErrCtx(func(ctx context.Context) error {
				return handleWaitingTextJobs(ctx, *src)
			}, ctx, "handle waiting text jobs")
		// We don't handle 'stop' here because we allow them to say 'stop' at any time, regardless of
		// phone status.
		//case "stop":
		default:
			content := "I have to start with either 'YES' or 'STOP' first, Which do you want?"
			err = sendTextCommandResponse(ctx, txn, *src, content)
			if err != nil {
				log.Error().Err(err).Msg("Failed to resend initial prompt.")
			}
		}
		return nil
	}
	switch body_l {
	case "stop":
		err = querycomms.PhoneUpdateStopMessageID(ctx, txn, src.PhoneString(), &l.ID)
		if err != nil {
			return fmt.Errorf("set phone stop message ID: %w", err)
		}
		content := "You have successfully been unsubscribed. You will not receive any more messages from this number. Reply START to resubscribe."
		err = sendTextCommandResponse(ctx, txn, *src, content)
		if err != nil {
			log.Error().Err(err).Msg("Failed to send unsubscribe acknowledgement.")
		}
		lint.LogOnErrCtx(func(ctx context.Context) error { return txn.Commit(ctx) }, ctx, "commit after confirm")
		return nil
	case "reset conversation":
		err = handleResetConversation(ctx, txn, *src)
		if err != nil {
			log.Error().Err(err).Msg("Failed to wipe memory")
			content := "Failed to wipe memory"
			lint.LogOnErrCtx(func(ctx context.Context) error {
				return sendTextCommandResponse(ctx, txn, *src, content)
			}, ctx, "send text command response")
			return fmt.Errorf("reset conversation: %w", err)
		}
		lint.LogOnErrCtx(func(ctx context.Context) error { return txn.Commit(ctx) }, ctx, "commit after confirm")
		return nil
	}
	// If we've got an open public report from this phone number then we'll let the district respond
	// Get the list of reports that are still open for a particular text message recipient
	// 'still open' is not well-defined throughout the system, but for now we'll go with
	// 'not reviewed in any way'.
	reports, err := querypublicreport.ReportsFromReporterPhone(ctx, txn, src.PhoneString())
	if err != nil {
		return fmt.Errorf("has open report: %w", err)
	}
	for _, report := range reports {
		_, err = querypublicreport.ReportLogInsert(ctx, txn, modelpublicreport.ReportLog{
			Created:    time.Now(),
			EmailLogID: nil,
			// ID
			ReportID:  report.ID,
			TextLogID: &log_id,
			Type:      modelpublicreport.Reportlogtype_MessageText,
			UserID:    nil,
		})
		if err != nil {
			return fmt.Errorf("insert report log: %w", err)
		}
		event.Updated(event.TypeRMOPublicReport, report.OrganizationID, report.PublicID)
	}
	lint.LogOnErrCtx(func(ctx context.Context) error { return txn.Commit(ctx) }, ctx, "commit after confirm")
	// If humans are involved, wait for them.
	if len(reports) > 0 {
		return nil
	}
	// Otherwise let the LLM handle the response
	if config.DoLLMResponse {
		return respondTextLLM(ctx, *src)
	}
	return nil
}

func respondTextLLM(ctx context.Context, src types.E164) error {
	previous_messages, err := loadPreviousMessagesForLLM(ctx, src)
	if err != nil {
		return fmt.Errorf("Failed to get previous messages: %w", err)
	}
	log.Info().Int("len", len(previous_messages)).Msg("passing")
	next_message, err := generateNextMessage(ctx, previous_messages, src)
	if err != nil {
		return fmt.Errorf("Failed to generate next message: %w", err)
	}
	txn, err := db.BeginTxn(ctx)
	if err != nil {
		return fmt.Errorf("start txn: %w", err)
	}
	defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")
	_, err = sendTextDirect(ctx, txn, modelcomms.Textorigin_Llm, src.PhoneString(), next_message.Content, true, false)
	if err != nil {
		return fmt.Errorf("Failed to send response text: %w", err)
	}
	if err := txn.Commit(ctx); err != nil {
		return fmt.Errorf("commit: %w", err)
	}
	return nil
}

func ParsePhoneNumber(input string) (*types.E164, error) {
	n, err := phonenumbers.Parse(input, "US")
	if err != nil {
		return nil, err
	}
	return types.NewE164(n), nil
}

func StoreSources() error {
	var err error
	ctx := context.TODO()
	txn := db.PGInstance.PGXPool
	// Magical id 1 is set in migration 00151
	contact, err := querycomms.ContactFromID(ctx, txn, 1)
	if err != nil {
		return fmt.Errorf("contact from ID 1: %w", err)
	}
	for _, n := range []string{config.PhoneNumberReportStr, config.PhoneNumberSupportStr, config.VoipMSNumber} {
		// Deal with Voip.ms not expecting API calls with the prefixed +1
		if !strings.HasPrefix(n, "+1") {
			dest, err := ParsePhoneNumber("+1" + n)
			if err != nil {
				return fmt.Errorf("Failed to parse +1'%s' as phone number: %w", n, err)
			}
			err = EnsureInDB(ctx, txn, contact, *dest)
		} else {
			dest, err := ParsePhoneNumber(n)
			if err != nil {
				return fmt.Errorf("Failed to parse '%s' as phone number: %w", n, err)
			}
			err = EnsureInDB(ctx, txn, contact, *dest)
		}
		if err != nil {
			return fmt.Errorf("Failed to add number '%s' to DB: %w", n, err)
		}
	}
	return nil
}

func UpdateMessageStatus(twilio_sid string, status string) {
	ctx := context.TODO()
	l, err := models.CommsTextLogs.Query(
		models.SelectWhere.CommsTextLogs.TwilioSid.EQ(twilio_sid),
	).One(ctx, db.PGInstance.BobDB)
	if err != nil {
		log.Error().Err(err).Str("twilio_sid", twilio_sid).Str("status", status).Msg("Failed to update message status query failed")
		return
	}
	err = l.Update(ctx, db.PGInstance.BobDB, &models.CommsTextLogSetter{
		TwilioStatus: omit.From(status),
	})
	if err != nil {
		log.Error().Err(err).Str("twilio_sid", twilio_sid).Str("status", status).Msg("Failed to update message status update failed")
		return
	}
}
