package resource

import (
	"context"
	"fmt"
	"net/http"
	"strconv"
	"time"

	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"
	nhttp "source.gleipnir.technology/Gleipnir/nidus-sync/http"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform"
	//"github.com/rs/zerolog/log"
)

type communicationR struct {
	router *router
}

func Communication(r *router) *communicationR {
	return &communicationR{
		router: r,
	}
}

type communicationLogType string
type communicationType string

const (
	communicationTypeUnknown          communicationType = "unknown"
	communicationTypeReportCompliance communicationType = "publicreport.compliance"
	communicationTypeReportNuisance   communicationType = "publicreport.nuisance"
	communicationTypeReportWater      communicationType = "publicreport.water"
)

type communicationLog struct {
	Created time.Time            `json:"created"`
	ID      string               `json:"id"`
	Type    communicationLogType `json:"type"`
	User    string               `json:"user"`
}
type communication struct {
	Created  time.Time          `json:"created"`
	ID       string             `json:"id"`
	Log      []communicationLog `json:"log"`
	Related  []resourceStub     `json:"related"`
	Response string             `json:"response"`
	Source   string             `json:"source"`
	Status   string             `json:"status"`
	Type     communicationType  `json:"type"`
	URI      string             `json:"uri"`
}
type resourceType string

const (
	resourceTypeUnknown          resourceType = "unknown"
	resourceTypeEmail            resourceType = "email"
	resourceTypeReportCompliance resourceType = "publicreport.compliance"
	resourceTypeReportNuisance   resourceType = "publicreport.nuisance"
	resourceTypeReportWater      resourceType = "publicreport.water"
	resourceTypeText             resourceType = "text"
)

type resourceStub struct {
	Created time.Time    `json:"created"`
	Type    resourceType `json:"type"`
	URI     string       `json:"uri"`
}

func (res *communicationR) Get(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (communication, *nhttp.ErrorWithStatus) {
	comm_id, error_with_status := res.router.IDFromMux(r)
	if error_with_status != nil {
		return communication{}, error_with_status
	}
	return res.hydratedCommunicationFromID(ctx, user, int32(comm_id))
}
func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]communication, *nhttp.ErrorWithStatus) {
	comms, err := platform.CommunicationsForOrganization(ctx, int64(user.Organization.ID))
	if err != nil {
		return nil, nhttp.NewError("nuisance report query: %w", err)
	}
	report_ids := make([]int64, 0)
	for _, comm := range comms {
		if comm.SourceReportID != nil {
			report_ids = append(report_ids, int64(*comm.SourceReportID))
		}
	}
	public_reports, err := platform.PublicReportsFromIDs(ctx, report_ids)
	if err != nil {
		return nil, nhttp.NewError("public reports from IDs: %w", err)
	}
	public_report_id_to_report := make(map[int32]modelpublicreport.Report, 0)
	for _, pr := range public_reports {
		public_report_id_to_report[pr.ID] = pr
	}
	result := make([]communication, len(comms))
	for i, comm := range comms {
		public_report, ok := public_report_id_to_report[*comm.SourceReportID]
		if !ok {
			return nil, nhttp.NewError("lookup report id %d failed", comm.SourceReportID)
		}
		related_records, err := platform.CommunicationRelatedRecords(ctx, user, &comm)
		if err != nil {
			return nil, nhttp.NewError("related records: %w", err)
		}
		c, e := res.hydrateCommunication(comm, related_records, &public_report)
		if e != nil {
			return nil, e
		}
		result[i] = c
	}
	return result, nil
}

type communicationMarkRequest struct{}

func (res *communicationR) MarkInvalid(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) {
	return res.markCommunication(ctx, r, user, "invalid", platform.CommunicationMarkInvalid)
}
func (res *communicationR) MarkPendingResponse(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) {
	return res.markCommunication(ctx, r, user, "pending-response", platform.CommunicationMarkPendingResponse)
}
func (res *communicationR) MarkPossibleIssue(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) {
	return res.markCommunication(ctx, r, user, "possible-issue", platform.CommunicationMarkPossibleIssue)
}
func (res *communicationR) MarkPossibleResolved(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) {
	return res.markCommunication(ctx, r, user, "possible-resolved", platform.CommunicationMarkPossibleResolved)
}
func (res *communicationR) hydrateCommunication(comm modelpublic.Communication, related_records []platform.RelatedRecord, public_report *modelpublicreport.Report) (communication, *nhttp.ErrorWithStatus) {
	var err error
	source_uri := "unknown"
	type_ := communicationTypeUnknown
	if comm.SourceReportID != nil && public_report != nil {
		source_uri, err = reportURI(res.router, "", public_report.PublicID)
		if err != nil {
			return communication{}, nhttp.NewError("gen report URI: %w", err)
		}
		switch public_report.ReportType {
		case modelpublicreport.Reporttype_Compliance:
			type_ = communicationTypeReportCompliance
		case modelpublicreport.Reporttype_Nuisance:
			type_ = communicationTypeReportNuisance
		case modelpublicreport.Reporttype_Water:
			type_ = communicationTypeReportWater
		default:
			type_ = communicationTypeUnknown
		}
	} else if comm.SourceEmailLogID != nil {
		source_uri, err = emailURI(*res.router, *comm.SourceEmailLogID)
		if err != nil {
			return communication{}, nhttp.NewError("gen email URI: %w", err)
		}
		type_ = "email"
	} else if comm.SourceTextLogID != nil {
		source_uri, err = textURI(*res.router, *comm.SourceTextLogID)
		if err != nil {
			return communication{}, nhttp.NewError("gen email URI: %w", err)
		}
		source_uri = "text"
	}
	uri, err := res.router.IDToURI("communication.ByIDGet", int(comm.ID))
	if err != nil {
		return communication{}, nhttp.NewError("gen comm uri: %w", err)
	}
	related := make([]resourceStub, len(related_records))
	for i, rr := range related_records {
		var uri string
		var r_type resourceType
		switch rr.Type {
		case platform.RelatedRecordTypeEmail:
			r_type = resourceTypeEmail
			uri, err = res.router.IDStrToURI("email.ByIDGet", rr.ID)
		case platform.RelatedRecordTypeReportCompliance:
			r_type = resourceTypeReportCompliance
			uri, err = res.router.IDStrToURI("publicreport.compliance.ByIDGet", rr.ID)
		case platform.RelatedRecordTypeReportNuisance:
			r_type = resourceTypeReportNuisance
			uri, err = res.router.IDStrToURI("publicreport.nuisance.ByIDGet", rr.ID)
		case platform.RelatedRecordTypeReportWater:
			r_type = resourceTypeReportWater
			uri, err = res.router.IDStrToURI("publicreport.water.ByIDGet", rr.ID)
		case platform.RelatedRecordTypeText:
			r_type = resourceTypeText
			uri, err = res.router.IDStrToURI("text.ByIDGet", rr.ID)
		default:
			r_type = resourceTypeUnknown
			err = fmt.Errorf("unrecognized related record type '%s'", rr.Type)
		}
		if err != nil {
			return communication{}, nhttp.NewError("related record hydration: %w", err)
		}
		related[i] = resourceStub{
			Created: rr.Created,
			Type:    r_type,
			URI:     uri,
		}
	}
	response, err := responseURI(*res.router, comm)
	if err != nil {
		return communication{}, nhttp.NewError("gen response URI: %w", err)
	}
	return communication{
		Created:  comm.Created,
		ID:       strconv.Itoa(int(comm.ID)),
		Log:      []communicationLog{},
		Related:  related,
		Response: response,
		Source:   source_uri,
		Status:   comm.Status.String(),
		Type:     type_,
		URI:      uri,
	}, nil
}

func (res *communicationR) hydratedCommunicationFromID(ctx context.Context, user platform.User, comm_id int32) (communication, *nhttp.ErrorWithStatus) {
	result, err := platform.CommunicationFromID(ctx, user, int64(comm_id))
	if result == nil {
		return communication{}, nhttp.NewUnauthorized("you are not authorized to modify communication %d", comm_id)
	}
	comm, err := platform.CommunicationFromID(ctx, user, int64(comm_id))
	if err != nil {
		return communication{}, nhttp.NewError("comm from ID: %w", err)
	}
	var public_report modelpublicreport.Report
	if comm.SourceReportID != nil {
		comm_ids := []int64{int64(*comm.SourceReportID)}
		public_reports, err := platform.PublicReportsFromIDs(ctx, comm_ids)
		if err != nil {
			return communication{}, nhttp.NewError("Get report %d: %w", *comm.SourceReportID, err)
		}
		public_report = public_reports[0]
	}
	related_records, err := platform.CommunicationRelatedRecords(ctx, user, comm)
	if err != nil {
		return communication{}, nhttp.NewError("related records: %w", err)
	}

	return res.hydrateCommunication(*comm, related_records, &public_report)
}

type markFunc = func(context.Context, platform.User, int32) error

func (res *communicationR) markCommunication(ctx context.Context, r *http.Request, user platform.User, status string, m markFunc) (communication, *nhttp.ErrorWithStatus) {
	comm_id, err_with_status := res.router.IDFromMux(r)
	if err_with_status != nil {
		return communication{}, err_with_status
	}
	err := m(ctx, user, int32(comm_id))
	if err != nil {
		return communication{}, nhttp.NewError("failed to mark %d: %w", comm_id, err)
	}
	return res.hydratedCommunicationFromID(ctx, user, int32(comm_id))
}
func responseURI(r router, comm modelpublic.Communication) (string, error) {
	if comm.ResponseEmailLogID != nil {
		return emailURI(r, *comm.ResponseEmailLogID)
	} else if comm.ResponseTextLogID != nil {
		return textURI(r, *comm.ResponseTextLogID)
	} else {
		return "", nil
	}
}
func emailURI(r router, id int32) (string, error) {
	return "fake email uri", nil
}

func textURI(r router, id int32) (string, error) {
	return "fake text uri", nil
}
