package service

import (
	"context"

	"github.com/go-errors/errors"

	"github.com/google/uuid"
	"github.com/phishingclub/phishingclub/data"
	"github.com/phishingclub/phishingclub/errs"
	"github.com/phishingclub/phishingclub/model"
	"github.com/phishingclub/phishingclub/repository"
	"github.com/phishingclub/phishingclub/validate"
	"gorm.io/gorm"
)

// Page is a Page service
type Page struct {
	Common
	PageRepository          *repository.Page
	CampaignRepository      *repository.Campaign
	CampaignTemplateService *CampaignTemplate
	TemplateService         *Template
	DomainRepository        *repository.Domain
}

// Create creates a new page
func (p *Page) Create(
	ctx context.Context,
	session *model.Session,
	page *model.Page,
) (*uuid.UUID, error) {
	ae := NewAuditEvent("Page.Create", session)
	// check permissions
	isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
	if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) {
		p.LogAuthError(err)
		return nil, errs.Wrap(err)
	}
	if !isAuthorized {
		p.AuditLogNotAuthorized(ae)
		return nil, errs.ErrAuthorizationFailed
	}
	var companyID *uuid.UUID
	if cid, err := page.CompanyID.Get(); err == nil {
		companyID = &cid
	}

	// validate data
	if err := page.Validate(); err != nil {
		p.Logger.Errorw("failed to validate page", "error", err)
		return nil, errs.Wrap(err)
	}
	// validate template content
	if content, err := page.Content.Get(); err == nil {
		if err := p.TemplateService.ValidatePageTemplate(content.String()); err != nil {
			p.Logger.Errorw("failed to validate page template", "error", err)
			return nil, validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content")
		}
	}
	// check uniqueness
	name := page.Name.MustGet()
	isOK, err := repository.CheckNameIsUnique(
		ctx,
		p.PageRepository.DB,
		"pages",
		name.String(),
		companyID,
		nil,
	)
	if err != nil {
		p.Logger.Errorw("failed to check page uniqueness", "error", err)
		return nil, errs.Wrap(err)
	}
	if !isOK {
		p.Logger.Debugw("page name is already taken", "name", name.String())
		return nil, validate.WrapErrorWithField(errors.New("is not unique"), "name")
	}
	// create page
	id, err := p.PageRepository.Insert(
		ctx,
		page,
	)
	if err != nil {
		p.Logger.Errorw("failed to create page", "error", err)
		return nil, errs.Wrap(err)
	}
	ae.Details["id"] = id.String()
	p.AuditLogAuthorized(ae)

	return id, nil
}

// GetAll gets pages
func (p *Page) GetAll(
	ctx context.Context,
	session *model.Session,
	companyID *uuid.UUID,
	options *repository.PageOption,
) (*model.Result[model.Page], error) {
	result := model.NewEmptyResult[model.Page]()
	ae := NewAuditEvent("Page.GetAll", session)
	// check permissions
	isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
	if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) {
		p.LogAuthError(err)
		return result, errs.Wrap(err)
	}
	if !isAuthorized {
		p.AuditLogNotAuthorized(ae)
		return result, errs.ErrAuthorizationFailed
	}
	result, err = p.PageRepository.GetAll(
		ctx,
		companyID,
		options,
	)
	if err != nil {
		p.Logger.Errorw("failed to get pages", "error", err)
		return result, errs.Wrap(err)
	}
	// no audit log on read
	return result, nil
}

// GetByID gets a page by ID
func (p *Page) GetByID(
	ctx context.Context,
	session *model.Session,
	id *uuid.UUID,
	options *repository.PageOption,
) (*model.Page, error) {
	ae := NewAuditEvent("Page.GetByID", session)
	ae.Details["id"] = id.String()
	// check permissions
	isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
	if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) {
		p.LogAuthError(err)
		return nil, errs.Wrap(err)
	}
	if !isAuthorized {
		p.AuditLogNotAuthorized(ae)
		return nil, errs.ErrAuthorizationFailed
	}
	// get page
	page, err := p.PageRepository.GetByID(
		ctx,
		id,
		options,
	)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		// return early this is not a an error
		return nil, errs.Wrap(err)
	}
	if err != nil {
		p.Logger.Errorw("failed to get page by ID", "error", err)
		return nil, errs.Wrap(err)
	}
	// no audit log on read

	return page, nil
}

// GetByCompanyID gets a page by company ID
func (p *Page) GetByCompanyID(
	ctx context.Context,
	session *model.Session,
	companyID *uuid.UUID,
	options *repository.PageOption,
) (*model.Result[model.Page], error) {
	result := model.NewEmptyResult[model.Page]()
	ae := NewAuditEvent("Page.GetByCompanyID", session)
	if companyID != nil {
		ae.Details["companyId"] = companyID.String()
	}
	// check permissions
	isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
	if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) {
		p.LogAuthError(err)
		return result, errs.Wrap(err)
	}
	if !isAuthorized {
		p.AuditLogNotAuthorized(ae)
		return result, errs.ErrAuthorizationFailed
	}
	// get pages
	result, err = p.PageRepository.GetAllByCompanyID(
		ctx,
		companyID,
		&repository.PageOption{},
	)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		// return early this is not a an error
		return result, errs.Wrap(err)
	}
	if err != nil {
		p.Logger.Errorw("failed to get page by company ID", "error", err)
		return result, errs.Wrap(err)
	}
	// no audit log on read

	return result, nil
}

// UpdateByID updates a page by ID
func (p *Page) UpdateByID(
	ctx context.Context,
	session *model.Session,
	id *uuid.UUID,
	page *model.Page,
) error {
	ae := NewAuditEvent("Page.UpdateByID", session)
	ae.Details["id"] = id.String()
	// check permissions
	isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
	if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) {
		p.LogAuthError(err)
		return err
	}
	if !isAuthorized {
		p.AuditLogNotAuthorized(ae)
		return errs.ErrAuthorizationFailed
	}
	// get current
	current, err := p.PageRepository.GetByID(
		ctx,
		id,
		&repository.PageOption{},
	)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		p.Logger.Debugw("failed to update page by ID", "error", err)
		return err
	}
	if err != nil {
		p.Logger.Errorw("failed to update page by ID", "error", err)
		return err
	}
	// update page - if a field is present and not null, update it
	if v, err := page.Name.Get(); err == nil {
		// check uniqueness
		var companyID *uuid.UUID
		if cid, err := current.CompanyID.Get(); err == nil {
			companyID = &cid
		}
		name := page.Name.MustGet()
		isOK, err := repository.CheckNameIsUnique(
			ctx,
			p.PageRepository.DB,
			"pages",
			name.String(),
			companyID,
			id,
		)
		if err != nil {
			p.Logger.Errorw("failed to check page uniqueness", "error", err)
			return err
		}
		if !isOK {
			p.Logger.Debugw("page name is already taken", "name", name.String())
			return validate.WrapErrorWithField(errors.New("is not unique"), "name")
		}
		current.Name.Set(v)
	}
	if v, err := page.Content.Get(); err == nil {
		current.Content.Set(v)
	}

	// validate template content
	if content, err := current.Content.Get(); err == nil {
		if err := p.TemplateService.ValidatePageTemplate(content.String()); err != nil {
			p.Logger.Errorw("failed to validate page template", "error", err)
			return validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content")
		}
	}
	// update page
	err = p.PageRepository.UpdateByID(
		ctx,
		id,
		current,
	)
	if err != nil {
		p.Logger.Errorw("failed to update page by ID", "error", err)
		return err
	}
	ae.Details["id"] = id.String()
	p.AuditLogNotAuthorized(ae)

	return nil
}

// DeleteByID deletes a page by ID
func (p *Page) DeleteByID(
	ctx context.Context,
	session *model.Session,
	id *uuid.UUID,
) error {
	ae := NewAuditEvent("Page.DeleteByID", session)
	ae.Details["id"] = id.String()
	// check permissions
	isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
	if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) {
		p.LogAuthError(err)
		return err
	}
	if !isAuthorized {
		p.AuditLogNotAuthorized(ae)
		return errs.ErrAuthorizationFailed
	}
	// remove the relation to campaign allow deny PageRepository
	err = p.CampaignRepository.RemoveDenyPageByDenyPageIDs(
		ctx,
		[]*uuid.UUID{id},
	)
	// delete the relation from campaign templates
	err = p.CampaignTemplateService.RemovePagesByPageID(
		ctx,
		session,
		id,
	)
	if err != nil {
		p.Logger.Errorw("failed to remove page ID relations from campaign templates", "error", err)
		return err
	}
	// delete page
	err = p.PageRepository.DeleteByID(
		ctx,
		id,
	)
	if err != nil {
		p.Logger.Errorw("failed to delete page by ID", "error", err)
		return err
	}
	p.AuditLogAuthorized(ae)

	return nil
}
