package drive

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/ultisuite/ulti-backend/internal/drivestore"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
	"github.com/ultisuite/ulti-backend/internal/orgpolicy"
)

var ErrOAuthNotConfigured = errors.New("oauth provider not configured")

type MountView struct {
	ID          string  `json:"id"`
	Scope       string  `json:"scope"`
	OrgSlug     *string `json:"org_slug,omitempty"`
	DisplayName string  `json:"display_name"`
	BackendType string  `json:"backend_type"`
	MountPoint  string  `json:"mount_point"`
	Status      string  `json:"status"`
	LastError   string  `json:"last_error,omitempty"`
	NCMountID   *int    `json:"nc_mount_id,omitempty"`
	NeedsOAuth  bool    `json:"needs_oauth,omitempty"`
}

type CreateMountParams struct {
	Scope        string
	OrgSlug      string
	DisplayName  string
	BackendType  string
	WebDAV       *nextcloud.WebDAVMountConfig
	OAuthBackend string
	OAuthAuth    string
}

func oauthProviderForBackend(backendType string) (providerKey, ncBackend, authBackend string, isOAuth bool) {
	switch strings.TrimSpace(strings.ToLower(backendType)) {
	case "googledrive", "google":
		return orgpolicy.MountOAuthProviderGoogle, "googledrive", "oauth2::google", true
	case "dropbox":
		return orgpolicy.MountOAuthProviderDropbox, "dropbox", "oauth2::dropbox", true
	case "onedrive", "microsoft":
		return orgpolicy.MountOAuthProviderMicrosoft, "onedrive", "oauth2::microsoft", true
	default:
		return "", "", "", false
	}
}

func (s *Service) orgPolicyLoader() *orgpolicy.Loader {
	return orgpolicy.NewLoader(s.db, nil)
}

func (s *Service) ListOrgMountsAdmin(ctx context.Context) ([]MountView, error) {
	store := s.ensureStore()
	if store == nil {
		return nil, fmt.Errorf("store not configured")
	}
	mounts, err := store.ListOrgMounts(ctx)
	if err != nil {
		return nil, err
	}
	out := make([]MountView, 0, len(mounts))
	for _, m := range mounts {
		out = append(out, mapMountView(m))
	}
	return out, nil
}

func (s *Service) CreateOrgWebDAVMount(ctx context.Context, orgSlug, displayName string, webdav nextcloud.WebDAVMountConfig) (MountView, error) {
	return s.CreateMount(ctx, "", "", CreateMountParams{
		Scope:       "org",
		OrgSlug:     orgSlug,
		DisplayName: displayName,
		BackendType: "webdav",
		WebDAV:      &webdav,
	})
}

func (s *Service) ListMountsForUser(ctx context.Context, platformUserID, ncUserID string, orgSlugs []string) ([]MountView, error) {
	store := s.ensureStore()
	if store == nil {
		return nil, fmt.Errorf("store not configured")
	}
	mounts, err := store.ListMountsForUser(ctx, platformUserID, orgSlugs)
	if err != nil {
		return nil, err
	}
	out := make([]MountView, 0, len(mounts))
	for _, m := range mounts {
		out = append(out, mapMountView(m))
	}
	return out, nil
}

func mapMountView(m drivestore.Mount) MountView {
	view := MountView{
		ID:          m.ID,
		Scope:       m.Scope,
		OrgSlug:     m.OrgSlug,
		DisplayName: m.DisplayName,
		BackendType: m.BackendType,
		MountPoint:  m.MountPoint,
		Status:      m.Status,
		LastError:   m.LastError,
		NCMountID:   m.NCMountID,
	}
	if m.Status == "pending_oauth" {
		view.NeedsOAuth = true
	}
	return view
}

func (s *Service) CreateMount(ctx context.Context, platformUserID, ncUserID string, p CreateMountParams) (MountView, error) {
	store := s.ensureStore()
	if store == nil {
		return MountView{}, fmt.Errorf("store not configured")
	}
	displayName := strings.TrimSpace(p.DisplayName)
	backendType := strings.TrimSpace(strings.ToLower(p.BackendType))
	if displayName == "" || backendType == "" {
		return MountView{}, ErrInvalid
	}
	mountPoint := "/" + strings.Trim(displayName, "/")
	scope := strings.TrimSpace(strings.ToLower(p.Scope))
	if scope != "user" && scope != "org" {
		return MountView{}, ErrInvalid
	}

	var ncMountID int
	var err error
	var configEnc []byte
	status := "active"

	providerKey, ncBackend, authBackend, isOAuth := oauthProviderForBackend(backendType)
	if isOAuth {
		creds, err := s.orgPolicyLoader().MountOAuthCredentials(ctx, providerKey)
		if err != nil {
			return MountView{}, err
		}
		if !creds.Enabled {
			return MountView{}, ErrOAuthNotConfigured
		}
		oauthConfig := map[string]string{
			"client_id":     creds.ClientID,
			"client_secret": creds.ClientSecret,
			"configured":    "false",
			"token":         "",
		}
		configEnc, _ = json.Marshal(oauthConfig)
		if scope == "org" {
			return MountView{}, ErrInvalid
		}
		ncMountID, err = s.nc.CreateOAuthExternalMount(ctx, ncUserID, mountPoint, ncBackend, authBackend, oauthConfig)
		status = "pending_oauth"
	} else {
		switch backendType {
		case "webdav", "dav":
			if p.WebDAV == nil {
				return MountView{}, ErrInvalid
			}
			configEnc, _ = json.Marshal(p.WebDAV)
			if scope == "org" {
				ncMountID, err = s.nc.CreateGlobalWebDAVMount(ctx, mountPoint, *p.WebDAV)
			} else {
				ncMountID, err = s.nc.CreateUserWebDAVMount(ctx, ncUserID, mountPoint, *p.WebDAV)
			}
		case "googledrive", "google", "dropbox", "onedrive", "microsoft":
			return MountView{}, ErrOAuthNotConfigured
		default:
			if p.OAuthBackend != "" {
				auth := p.OAuthAuth
				if auth == "" {
					auth = "oauth2::" + backendType
				}
				ncMountID, err = s.nc.CreateOAuthExternalMount(ctx, ncUserID, mountPoint, p.OAuthBackend, auth, nil)
				status = "pending_oauth"
			} else {
				return MountView{}, ErrInvalid
			}
		}
	}

	lastError := ""
	if err != nil {
		status = "error"
		lastError = err.Error()
	}

	var ownerID *string
	var orgSlug *string
	if scope == "user" {
		ownerID = &platformUserID
	} else {
		slug := strings.TrimSpace(strings.ToLower(p.OrgSlug))
		if slug == "" {
			return MountView{}, ErrInvalid
		}
		orgSlug = &slug
	}

	var ncIDPtr *int
	if ncMountID > 0 {
		ncIDPtr = &ncMountID
	}
	row, err := store.CreateMount(ctx, drivestore.CreateMountParams{
		Scope:       scope,
		OwnerUserID: ownerID,
		OrgSlug:     orgSlug,
		NCMountID:   ncIDPtr,
		DisplayName: displayName,
		BackendType: backendType,
		MountPoint:  mountPoint,
		Status:      status,
		ConfigEnc:   configEnc,
	})
	if err != nil {
		if ncMountID > 0 {
			_ = s.nc.DeleteExternalMount(ctx, ncMountID)
		}
		return MountView{}, err
	}
	if status == "error" {
		_ = store.UpdateMountStatus(ctx, row.ID, status, lastError, ncIDPtr)
	}
	return mapMountView(row), nil
}

func (s *Service) GetMountAdmin(ctx context.Context, mountID string) (MountView, error) {
	store := s.ensureStore()
	if store == nil {
		return MountView{}, fmt.Errorf("store not configured")
	}
	mount, err := store.GetMount(ctx, mountID)
	if err != nil {
		if errors.Is(err, drivestore.ErrMountNotFound) {
			return MountView{}, ErrNotFound
		}
		return MountView{}, err
	}
	return mapMountView(mount), nil
}

func (s *Service) DeleteMount(ctx context.Context, mountID string) error {
	store := s.ensureStore()
	if store == nil {
		return fmt.Errorf("store not configured")
	}
	mount, err := store.GetMount(ctx, mountID)
	if err != nil {
		return err
	}
	if mount.NCMountID != nil && *mount.NCMountID > 0 {
		if err := s.nc.DeleteExternalMount(ctx, *mount.NCMountID); err != nil {
			return mapDriveError(err)
		}
	}
	return store.DeleteMount(ctx, mountID)
}

func (s *Service) GetMountOAuthURL(ctx context.Context, mountID, platformUserID, ncUserID, redirectURI string) (string, error) {
	if err := validateMountOAuthRedirectURI(redirectURI); err != nil {
		return "", err
	}
	store := s.ensureStore()
	if store == nil {
		return "", fmt.Errorf("store not configured")
	}
	mount, err := store.GetMount(ctx, mountID)
	if err != nil {
		return "", err
	}
	if mount.OwnerUserID == nil || *mount.OwnerUserID != platformUserID {
		return "", ErrForbidden
	}
	if mount.NCMountID == nil {
		return "", ErrInvalid
	}
	providerKey, _, _, isOAuth := oauthProviderForBackend(mount.BackendType)
	if !isOAuth {
		return "", ErrInvalid
	}
	creds, err := s.orgPolicyLoader().MountOAuthCredentials(ctx, providerKey)
	if err != nil {
		return "", err
	}
	if !creds.Enabled {
		return "", ErrOAuthNotConfigured
	}
	return s.nc.StartExternalStorageOAuth2(ctx, ncUserID, creds.ClientID, creds.ClientSecret, redirectURI)
}

func (s *Service) CompleteMountOAuth(ctx context.Context, mountID, platformUserID, ncUserID, redirectURI, code string) error {
	if err := validateMountOAuthRedirectURI(redirectURI); err != nil {
		return err
	}
	store := s.ensureStore()
	if store == nil {
		return fmt.Errorf("store not configured")
	}
	code = strings.TrimSpace(code)
	if code == "" {
		return ErrInvalid
	}
	mount, err := store.GetMount(ctx, mountID)
	if err != nil {
		return err
	}
	if mount.OwnerUserID == nil || *mount.OwnerUserID != platformUserID {
		return ErrForbidden
	}
	if mount.NCMountID == nil {
		return ErrInvalid
	}
	providerKey, _, _, isOAuth := oauthProviderForBackend(mount.BackendType)
	if !isOAuth {
		return ErrInvalid
	}
	creds, err := s.orgPolicyLoader().MountOAuthCredentials(ctx, providerKey)
	if err != nil {
		return err
	}
	if !creds.Enabled {
		return ErrOAuthNotConfigured
	}
	token, err := s.nc.CompleteExternalStorageOAuth2(ctx, ncUserID, creds.ClientID, creds.ClientSecret, redirectURI, code)
	if err != nil {
		_ = store.UpdateMountStatus(ctx, mount.ID, "error", err.Error(), mount.NCMountID)
		return err
	}
	if err := s.nc.UpdateUserExternalMountOAuth(ctx, ncUserID, *mount.NCMountID, creds.ClientID, creds.ClientSecret, token); err != nil {
		_ = store.UpdateMountStatus(ctx, mount.ID, "error", err.Error(), mount.NCMountID)
		return err
	}
	configEnc, _ := store.GetMountConfig(ctx, mount.ID)
	cfg := map[string]any{}
	if len(configEnc) > 0 {
		_ = json.Unmarshal(configEnc, &cfg)
	}
	cfg["token"] = token
	cfg["configured"] = true
	cfg["client_id"] = creds.ClientID
	cfg["client_secret"] = creds.ClientSecret
	if merged, err := json.Marshal(cfg); err == nil {
		_ = store.UpdateMountConfig(ctx, mount.ID, merged)
	}
	return store.UpdateMountStatus(ctx, mount.ID, "active", "", mount.NCMountID)
}
