package app

import (
	"net/http"
	"time"

	"github.com/caddyserver/certmagic"
	"github.com/phishingclub/phishingclub/service"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// Services is a collection of services
type Services struct {
	Asset               *service.Asset
	Attachment          *service.Attachment
	File                *service.File
	Company             *service.Company
	InstallSetup        *service.InstallSetup
	Option              *service.Option
	Page                *service.Page
	Proxy               *service.Proxy
	Session             *service.Session
	User                *service.User
	Domain              *service.Domain
	Recipient           *service.Recipient
	RecipientGroup      *service.RecipientGroup
	SMTPConfiguration   *service.SMTPConfiguration
	Email               *service.Email
	CampaignTemplate    *service.CampaignTemplate
	Campaign            *service.Campaign
	Template            *service.Template
	APISender           *service.APISender
	AllowDeny           *service.AllowDeny
	Webhook             *service.Webhook
	Identifier          *service.Identifier
	Version             *service.Version
	SSO                 *service.SSO
	Update              *service.Update
	Import              *service.Import
	Backup              *service.Backup
	IPAllowList         *service.IPAllowListService
	ProxySessionManager *service.ProxySessionManager
	OAuthProvider       *service.OAuthProvider
	MicrosoftDeviceCode *service.MicrosoftDeviceCode
	CompanyScimConfig   *service.CompanyScimConfig
	CompanyReportConfig *service.CompanyReportConfig
	Scim                *service.Scim
	RemoteBrowser       *service.RemoteBrowser
	ReportTemplate      *service.ReportTemplate
}

// NewServices creates a collection of services
func NewServices(
	db *gorm.DB,
	repositories *Repositories,
	logger *zap.SugaredLogger,
	utilities *Utilities,
	assetPath string,
	attachmentPath string,
	ownManagedCertificatePath string,
	enviroment string,
	certMagicConfig *certmagic.Config,
	certMagicCache *certmagic.Cache,
	filePath string,
	trustedProxies []string,
	remoteBrowserExecPath string,
) *Services {
	common := service.Common{
		Logger: logger,
	}
	microsoftDeviceCodeService := &service.MicrosoftDeviceCode{
		Common:                        common,
		MicrosoftDeviceCodeRepository: repositories.MicrosoftDeviceCode,
		CampaignRepository:            repositories.Campaign,
		CampaignRecipientRepository:   repositories.CampaignRecipient,
		HTTPClient:                    &http.Client{Timeout: 15 * time.Second},
	}
	templateService := &service.Template{
		Common:                     common,
		RecipientRepository:        repositories.Recipient,
		OptionRepository:           repositories.Option,
		RemoteBrowserRepository:    repositories.RemoteBrowser,
		MicrosoftDeviceCodeService: microsoftDeviceCodeService,
	}
	file := &service.File{
		Common: common,
	}
	asset := &service.Asset{
		Common:            common,
		RootFolder:        assetPath,
		FileService:       file,
		AssetRepository:   repositories.Asset,
		DomainRepository:  repositories.Domain,
		CompanyRepository: repositories.Company,
	}
	attachment := &service.Attachment{
		Common:               common,
		RootFolder:           attachmentPath,
		FileService:          file,
		AttachmentRepository: repositories.Attachment,
		EmailRepository:      repositories.Email,
	}
	installSetup := &service.InstallSetup{
		Common:            common,
		UserRepository:    repositories.User,
		RoleRepository:    repositories.Role,
		CompanyRepository: repositories.Company,
		PasswordHasher:    utilities.PasswordHasher,
	}
	sessionService := &service.Session{
		Common:            common,
		SessionRepository: repositories.Session,
	}
	optionService := &service.Option{
		Common:           common,
		OptionRepository: repositories.Option,
		DomainRepository: repositories.Domain,
	}
	userService := &service.User{
		Common:            common,
		UserRepository:    repositories.User,
		RoleRepository:    repositories.Role,
		CompanyRepository: repositories.Company,
		PasswordHasher:    utilities.PasswordHasher,
		TOTPReplayCache:   service.NewTOTPReplayCache(),
	}
	recipient := &service.Recipient{
		Common:                      common,
		RecipientRepository:         repositories.Recipient,
		RecipientGroupRepository:    repositories.RecipientGroup,
		CampaignRepository:          repositories.Campaign,
		CampaignRecipientRepository: repositories.CampaignRecipient,
	}

	recipientGroup := &service.RecipientGroup{
		Common:                      common,
		CampaignRepository:          repositories.Campaign,
		CampaignRecipientRepository: repositories.CampaignRecipient,
		RecipientGroupRepository:    repositories.RecipientGroup,
		RecipientRepository:         repositories.Recipient,
		RecipientService:            recipient,
		DB:                          db,
	}
	webhook := &service.Webhook{
		Common:             common,
		CampaignRepository: repositories.Campaign,
		WebhookRepository:  repositories.Webhook,
	}

	campaignTemplate := &service.CampaignTemplate{
		Common:                     common,
		CampaignTemplateRepository: repositories.CampaignTemplate,
		CampaignRepository:         repositories.Campaign,
		IdentifierRepository:       repositories.Identifier,
	}
	apiSender := &service.APISender{
		Common:                  common,
		APISenderRepository:     repositories.APISender,
		TemplateService:         templateService,
		CampaignTemplateService: campaignTemplate,
	}
	smtpConfiguration := &service.SMTPConfiguration{
		Common:                      common,
		SMTPConfigurationRepository: repositories.SMTPConfiguration,
		CampaignTemplateService:     campaignTemplate,
	}
	page := &service.Page{
		Common:                  common,
		CampaignRepository:      repositories.Campaign,
		PageRepository:          repositories.Page,
		CampaignTemplateService: campaignTemplate,
		TemplateService:         templateService,
		DomainRepository:        repositories.Domain,
	}
	domain := &service.Domain{
		Common:                    common,
		OwnManagedCertificatePath: ownManagedCertificatePath,
		CertMagicConfig:           certMagicConfig,
		CertMagicCache:            certMagicCache,
		DomainRepository:          repositories.Domain,
		CompanyRepository:         repositories.Company,
		OptionRepository:          repositories.Option,
		CampaignTemplateService:   campaignTemplate,
		AssetService:              asset,
		FileService:               file,
		TemplateService:           templateService,
	}
	proxySessionManager := service.NewProxySessionManager(logger)
	proxy := &service.Proxy{
		Common:                  common,
		ProxyRepository:         repositories.Proxy,
		DomainRepository:        repositories.Domain,
		CampaignRepository:      repositories.Campaign,
		CampaignTemplateService: campaignTemplate,
		DomainService:           domain,
		ProxySessionManager:     proxySessionManager,
	}
	ipAllowListService := service.NewIPAllowListService(logger, repositories.Proxy)
	email := &service.Email{
		Common:                  common,
		AttachmentPath:          attachmentPath,
		AttachmentService:       attachment,
		DomainService:           domain,
		EmailRepository:         repositories.Email,
		SMTPService:             smtpConfiguration,
		RecipientService:        recipient,
		TemplateService:         templateService,
		CampaignTemplateService: campaignTemplate,
	}
	remoteBrowser := &service.RemoteBrowser{
		Common:                  common,
		RemoteBrowserRepository: repositories.RemoteBrowser,
	}
	lureCode := &service.LureCode{
		CampaignRecipientRepository: repositories.CampaignRecipient,
		Logger:                      common.Logger,
	}
	campaign := &service.Campaign{
		Common:                        common,
		LureCodeService:               lureCode,
		CampaignRepository:            repositories.Campaign,
		CampaignRecipientRepository:   repositories.CampaignRecipient,
		RecipientRepository:           repositories.Recipient,
		RecipientGroupRepository:      repositories.RecipientGroup,
		AllowDenyRepository:           repositories.AllowDeny,
		WebhookRepository:             repositories.Webhook,
		CampaignTemplateService:       campaignTemplate,
		DomainService:                 domain,
		RecipientService:              recipient,
		MailService:                   email,
		APISenderService:              apiSender,
		SMTPConfigService:             smtpConfiguration,
		WebhookService:                webhook,
		TemplateService:               templateService,
		MicrosoftDeviceCodeRepository: repositories.MicrosoftDeviceCode,
		AttachmentPath:                attachmentPath,
		RemoteBrowserService:          remoteBrowser,
		ReportTemplateRepository:      repositories.ReportTemplate,
		CompanyReportConfigRepository: repositories.CompanyReportConfig,
		ReportSendLogRepository:       repositories.ReportSendLog,
		OptionService:                 optionService,
		RemoteBrowserExecPath:         remoteBrowserExecPath,
		TrustedProxies:                trustedProxies,
	}
	// wire campaign service into microsoft device code service now that campaign is constructed
	microsoftDeviceCodeService.CampaignService = campaign
	allowDeny := &service.AllowDeny{
		Common:              common,
		AllowDenyRepository: repositories.AllowDeny,
		CampaignRepository:  repositories.Campaign,
	}
	identifier := &service.Identifier{
		Common:               common,
		IdentifierRepository: repositories.Identifier,
	}
	companyService := &service.Company{
		Common:                   common,
		DomainService:            domain,
		PageService:              page,
		EmailService:             email,
		SMTPConfigurationService: smtpConfiguration,
		APISenderService:         apiSender,
		RecipientService:         recipient,
		RecipientGroupService:    recipientGroup,
		CampaignService:          campaign,
		CampaignTemplate:         campaignTemplate,
		AllowDenyService:         allowDeny,
		WebhookService:           webhook,
		CompanyRepository:        repositories.Company,
	}
	versionService := &service.Version{Common: common}
	ssoService := &service.SSO{
		Common:         common,
		OptionsService: optionService,
		UserService:    userService,
		SessionService: sessionService,
		// MSALClient:     msalClient, this dependency is set AFTER this function
	}
	backupService := &service.Backup{
		Common:        common,
		OptionService: optionService,
		DB:            db,
		FilePath:      filePath,
	}
	updateService := &service.Update{
		Common:        common,
		OptionService: optionService,
	}
	importService := &service.Import{
		Common:          common,
		Asset:           asset,
		Page:            page,
		Email:           email,
		File:            file,
		EmailRepository: repositories.Email,
		PageRepository:  repositories.Page,
	}
	oauthProvider := &service.OAuthProvider{
		Common:                  common,
		OAuthProviderRepository: repositories.OAuthProvider,
		OAuthStateRepository:    repositories.OAuthState,
	}

	// inject oauth provider service into api sender
	apiSender.OAuthProviderService = oauthProvider
	companyScimConfig := &service.CompanyScimConfig{
		Common:                      common,
		CompanyScimConfigRepository: repositories.CompanyScimConfig,
	}
	scim := &service.Scim{
		Common:                      common,
		CompanyScimConfigRepository: repositories.CompanyScimConfig,
		CompanyScimConfigService:    companyScimConfig,
		RecipientRepository:         repositories.Recipient,
		RecipientGroupRepository:    repositories.RecipientGroup,
		RecipientService:            recipient,
		OptionService:               optionService,
		CampaignRepository:          repositories.Campaign,
		CampaignRecipientRepository: repositories.CampaignRecipient,
	}

	reportTemplate := &service.ReportTemplate{
		Common:                   common,
		ReportTemplateRepository: repositories.ReportTemplate,
	}

	companyReportConfig := &service.CompanyReportConfig{
		Common:                        common,
		CompanyReportConfigRepository: repositories.CompanyReportConfig,
		ReportSendLogRepository:       repositories.ReportSendLog,
	}

	return &Services{
		CompanyScimConfig:   companyScimConfig,
		CompanyReportConfig: companyReportConfig,
		Scim:                scim,
		Asset:               asset,
		Attachment:          attachment,
		Company:             companyService,
		File:                file,
		InstallSetup:        installSetup,
		Option:              optionService,
		Page:                page,
		Proxy:               proxy,
		Session:             sessionService,
		User:                userService,
		Domain:              domain,
		Recipient:           recipient,
		RecipientGroup:      recipientGroup,
		SMTPConfiguration:   smtpConfiguration,
		Email:               email,
		Template:            templateService,
		CampaignTemplate:    campaignTemplate,
		Campaign:            campaign,
		APISender:           apiSender,
		AllowDeny:           allowDeny,
		Webhook:             webhook,
		Identifier:          identifier,
		Version:             versionService,
		SSO:                 ssoService,
		Update:              updateService,
		Import:              importService,
		Backup:              backupService,
		IPAllowList:         ipAllowListService,
		ProxySessionManager: proxySessionManager,
		OAuthProvider:       oauthProvider,
		MicrosoftDeviceCode: microsoftDeviceCodeService,
		RemoteBrowser:       remoteBrowser,
		ReportTemplate:      reportTemplate,
	}
}
