package publicreport

import (
	"context"
	"fmt"
	"time"

	"github.com/rs/zerolog/log"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db"
	modelcomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/comms/model"
	modelpublic "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/public/model"
	modelpublicreport "source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/publicreport/model"
	querycomms "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/comms"
	querypublic "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/public"
	querypublicreport "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/publicreport"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform/email"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform/text"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform/types"
)

func DistrictForReport(ctx context.Context, report_id string) (modelpublic.Organization, error) {
	report, err := querypublicreport.ReportFromPublicID(ctx, db.PGInstance.PGXPool, report_id)
	if err != nil {
		return modelpublic.Organization{}, fmt.Errorf("Failed to find report %s: %w", report_id, err)
	}
	result, e := querypublic.OrganizationFromID(ctx, db.PGInstance.PGXPool, int64(report.OrganizationID))
	if e != nil {
		return modelpublic.Organization{}, fmt.Errorf("Failed to load organization %d: %w", report.OrganizationID, e)
	}
	return result, nil
}

func RegisterNotificationEmail(ctx context.Context, txn db.Ex, report modelpublicreport.Report, contact modelcomms.Contact, destination string) error {
	err := email.EnsureInDB(ctx, txn, contact, destination)
	if err != nil {
		return fmt.Errorf("Failed to ensure phone is in DB: %w", err)
	}
	_, err = querypublicreport.NotifyEmailCreate(ctx, txn, report.ID, destination)
	if err != nil {
		return err
	}
	return email.SendReportConfirmation(ctx, txn, contact, destination, report.PublicID)
}

func RegisterNotificationPhone(ctx context.Context, txn db.Ex, report modelpublicreport.Report, contact modelcomms.Contact, phone types.E164) error {
	err := text.EnsureInDB(ctx, txn, contact, phone)
	if err != nil {
		return fmt.Errorf("Failed to ensure phone is in DB: %w", err)
	}
	_, err = querypublicreport.NotifyPhoneCreate(ctx, txn, report.ID, phone.PhoneString())
	if err != nil {
		return err
	}
	return text.ReportSubscriptionConfirmationText(ctx, db.PGInstance.PGXPool, phone, report.PublicID)
}

func RegisterSubscriptionEmail(ctx context.Context, txn db.Ex, contact modelcomms.Contact, destination string) error {
	_, err := querypublicreport.SubscribeEmailInsert(ctx, txn, modelpublicreport.SubscribeEmail{
		Created:      time.Now(),
		Deleted:      nil,
		EmailAddress: destination,
	})
	if err != nil {
		return fmt.Errorf("Failed to save new subscription email row: %w", err)
	}

	return nil
}
func RegisterSubscriptionPhone(ctx context.Context, txn db.Ex, contact modelcomms.Contact, phone types.E164) error {
	_, err := querypublicreport.SubscribePhoneInsert(ctx, txn, modelpublicreport.SubscribePhone{
		Created:   time.Now(),
		Deleted:   nil,
		PhoneE164: phone.PhoneString(),
	})
	if err != nil {
		return fmt.Errorf("Failed to save new subscription phone row: %w", err)
	}
	return nil
}

func SaveReporter(ctx context.Context, txn db.Ex, report modelpublicreport.Report, name string, email_address string, phone *types.E164, can_sms bool) (contact modelcomms.Contact, err error) {
	report_contact, err := querycomms.ContactFromID(ctx, txn, int64(report.ReporterContactID))
	if err != nil {
		return contact, fmt.Errorf("contact query: %w", err)
	}
	email_contact, err := querycomms.ContactFromEmail(ctx, txn, email_address)
	if err != nil {
		return contact, fmt.Errorf("contact query: %w", err)
	}
	phone_contact, err := querycomms.ContactFromPhone(ctx, txn, phone.PhoneString())
	if err != nil {
		return contact, fmt.Errorf("contact query: %w", err)
	}
	var new_contact_name string
	new_contact_id := report_contact.ID
	if phone_contact != nil {
		// Whether or not the contacts match, we're going to pick the phone contact
		// as the authoritative one, for no particular reason.
		new_contact_id = phone_contact.ID
		if phone_contact.Name != "" {
			new_contact_name = phone_contact.Name
		}
		if email_contact != nil && email_contact.ID != phone_contact.ID {
			err = querycomms.ContactEmailUpdateContactID(ctx, txn, email_address, int64(phone_contact.ID))
			if err != nil {
				return contact, fmt.Errorf("update email contact %d (%d) to %d: %w", email_contact.ID, email_contact.ID, phone_contact.ID, err)
			}
			log.Info().Str("address", email_address).Str("e164", phone.PhoneString()).Int32("from-id", email_contact.ID).Int32("to-id", phone_contact.ID).Msg("merged email contact with phone contact")
		}
	} else if email_contact != nil {
		new_contact_id = email_contact.ID
		if email_contact.Name != "" {
			new_contact_name = email_contact.Name
		}
	}
	if name != "" {
		new_contact_name = name
	}
	if new_contact_id != 0 && new_contact_id != report_contact.ID {
		err = querypublicreport.ReportUpdateReporterContactID(ctx, txn, int64(report.ID), int64(new_contact_id))
		if err != nil {
			return contact, fmt.Errorf("update report %d reporter contact from %d to %d: %w", report.ID, report.ReporterContactID, new_contact_id, err)
		}
	}
	if new_contact_name != "" {
		err = querycomms.ContactUpdateName(ctx, txn, int64(new_contact_id), new_contact_name)
		if err != nil {
			return contact, fmt.Errorf("update contact %d to name %s: %w", new_contact_id, new_contact_name, err)
		}
	}

	log.Debug().Str("name", name).Str("old name", contact.Name).Int32("id", contact.ID).Msg("contact updated")

	if email_address != "" && email_contact == nil {
		err = saveReporterEmail(ctx, txn, new_contact_id, email_address)
		if err != nil {
			return contact, fmt.Errorf("new contact email for '%s' on %d: %w", email_address, new_contact_id, err)
		}
	}
	if phone != nil && phone_contact == nil {
		err = saveReporterPhone(ctx, txn, new_contact_id, phone, can_sms)
		if err != nil {
			return contact, fmt.Errorf("new contact phone for '%s' on %d: %w", phone.PhoneString(), new_contact_id, err)
		}
	}
	return contact, nil
}
func saveReporterEmail(ctx context.Context, txn db.Ex, contact_id int32, email_address string) error {
	e, err := querycomms.ContactEmailInsert(ctx, txn, modelcomms.ContactEmail{
		Address:      email_address,
		Confirmed:    false,
		ContactID:    contact_id,
		IsSubscribed: false,
	})
	if err != nil {
		return fmt.Errorf("contact add email: %w", err)
	}
	log.Info().Str("address", e.Address).Int32("contact_id", contact_id).Msg("Created contact email")
	return nil
}

func saveReporterPhone(ctx context.Context, txn db.Ex, contact_id int32, number *types.E164, can_sms bool) error {
	if number == nil {
		return nil
	}
	_, err := querycomms.PhoneInsertIfNotExists(ctx, txn, modelcomms.Phone{
		E164:               number.PhoneString(),
		CanSms:             can_sms,
		ConfirmedMessageID: nil,
		StopMessageID:      nil,
	})
	if err != nil {
		return fmt.Errorf("insert phone if not exists: %w", err)
	}
	p, err := querycomms.ContactPhoneInsert(ctx, txn, modelcomms.ContactPhone{
		ContactID:    contact_id,
		E164:         number.PhoneString(),
		IsSubscribed: false,
	})
	if err != nil {
		return fmt.Errorf("contact add phone: %w", err)
	}
	log.Info().Str("e164", p.E164).Int32("contact_id", contact_id).Msg("Created contact phone")
	return nil
}
