package apitokens

import (
	"net/http"
	"strings"
)

type ScopeHint int

const (
	ScopeNone ScopeHint = iota
	ScopeMailAccountQuery
	ScopeMailAccountPath
	ScopeDrivePathFromURL
)

type Requirement struct {
	Resource     string
	Alternatives []string
	Write        bool
	ScopeHint    ScopeHint
}

func RequirementForRequest(method, fullPath, typesQuery string) (Requirement, bool) {
	method = strings.ToUpper(strings.TrimSpace(method))
	path := strings.TrimSuffix(strings.TrimSpace(fullPath), "/")
	if path == "" {
		path = "/"
	}

	write := method != http.MethodGet && method != http.MethodHead

	switch {
	case strings.HasPrefix(path, "/api/v1/mail/api-tokens"):
		return Requirement{Resource: "automation.api_tokens", Write: write || method == http.MethodDelete}, true
	case strings.HasPrefix(path, "/api/v1/mail/webhooks"):
		return Requirement{Resource: "automation.webhooks", Write: write}, true
	case strings.HasPrefix(path, "/api/v1/mail/rules"):
		return Requirement{Resource: "automation.rules", Write: write}, true

	case strings.HasPrefix(path, "/api/v1/contacts/discovery/llm-settings"),
		strings.HasPrefix(path, "/api/v1/contacts/discovery/llm-models/"):
		return Requirement{Resource: "automation.llm", Write: write}, true
	case strings.HasPrefix(path, "/api/v1/contacts/discovery/search-settings"):
		return Requirement{Resource: "automation.search", Write: write}, true
	case strings.HasPrefix(path, "/api/v1/contacts/discovery/"):
		return Requirement{Resource: "contacts.write", Write: write}, true

	case strings.HasPrefix(path, "/api/v1/contacts/search"):
		return Requirement{Resource: "contacts.search", Write: false}, true
	case strings.HasPrefix(path, "/api/v1/contacts/"):
		switch method {
		case http.MethodPost, http.MethodPut, http.MethodPatch:
			if strings.Contains(path, "/merge-duplicates") || strings.Contains(path, "/improve") {
				return Requirement{Resource: "contacts.write", Write: true}, true
			}
			if strings.Contains(path, "/books/") {
				return Requirement{Resource: "contacts.write", Write: true}, true
			}
			return Requirement{Resource: "contacts.write", Write: true}, true
		case http.MethodDelete:
			return Requirement{Resource: "contacts.delete", Write: true}, true
		default:
			return Requirement{Resource: "contacts.read", Write: false}, true
		}

	case strings.HasPrefix(path, "/api/v1/drive/"):
		return driveRequirement(method, path)

	case strings.HasPrefix(path, "/api/v1/search"):
		return searchRequirement(typesQuery)

	case strings.HasPrefix(path, "/api/v1/mail/"):
		return mailRequirement(method, path)
	}

	return Requirement{}, false
}

func mailRequirement(method, path string) (Requirement, bool) {
	write := method != http.MethodGet && method != http.MethodHead

	switch {
	case strings.HasPrefix(path, "/api/v1/mail/settings"):
		return Requirement{Resource: "mail.settings", Write: write}, true
	case strings.HasPrefix(path, "/api/v1/mail/search"):
		return Requirement{Resource: "mail.search", Write: false, ScopeHint: ScopeMailAccountQuery}, true
	case strings.HasPrefix(path, "/api/v1/mail/send"),
		strings.HasPrefix(path, "/api/v1/mail/outbox/"):
		return Requirement{Resource: "mail.send", Write: true}, true
	case strings.HasPrefix(path, "/api/v1/mail/signatures"):
		return Requirement{Resource: "mail.settings", Write: write}, true
	case strings.HasPrefix(path, "/api/v1/mail/identities/"):
		return Requirement{Resource: "mail.identities", Write: write}, true
	case strings.Contains(path, "/accounts/") && strings.Contains(path, "/identities"):
		return Requirement{Resource: "mail.identities", Write: write, ScopeHint: ScopeMailAccountPath}, true
	case strings.HasPrefix(path, "/api/v1/mail/accounts"):
		if write {
			return Requirement{Resource: "mail.settings", Write: true, ScopeHint: ScopeMailAccountPath}, true
		}
		return Requirement{Resource: "mail.mailboxes", Write: false, ScopeHint: ScopeMailAccountPath}, true
	case strings.HasPrefix(path, "/api/v1/mail/unified-folders"),
		strings.HasPrefix(path, "/api/v1/mail/folders"):
		return Requirement{Resource: "mail.mailboxes", Write: write, ScopeHint: ScopeMailAccountQuery}, true
	case strings.HasPrefix(path, "/api/v1/mail/labels"):
		return Requirement{Resource: "mail.labels", Write: write}, true
	case strings.HasPrefix(path, "/api/v1/mail/attachments/"),
		strings.Contains(path, "/attachments"):
		if write {
			return Requirement{Resource: "mail.attachments", Write: true}, true
		}
		return Requirement{Resource: "mail.attachments", Write: false}, true
	case strings.HasPrefix(path, "/api/v1/mail/messages"):
		if strings.HasSuffix(path, "/labels") || strings.HasSuffix(path, "/flags") {
			return Requirement{Resource: "mail.labels", Write: true}, true
		}
		if write {
			return Requirement{Resource: "mail.labels", Write: true}, true
		}
		return Requirement{Resource: "mail.messages", Write: false, ScopeHint: ScopeMailAccountQuery}, true
	case strings.HasPrefix(path, "/api/v1/mail/threads"):
		return Requirement{Resource: "mail.messages", Write: false}, true
	case strings.HasPrefix(path, "/api/v1/mail/drafts"):
		if write {
			return Requirement{Resource: "mail.send", Write: true}, true
		}
		return Requirement{Resource: "mail.messages", Write: false}, true
	default:
		return Requirement{}, false
	}
}

func driveRequirement(method, path string) (Requirement, bool) {
	write := method != http.MethodGet && method != http.MethodHead

	switch {
	case strings.Contains(path, "/preview/"):
		return Requirement{Resource: "drive.thumbnails", Write: false, ScopeHint: ScopeDrivePathFromURL}, true
	case strings.Contains(path, "/download/"):
		return Requirement{Resource: "drive.download", Write: false, ScopeHint: ScopeDrivePathFromURL}, true
	case strings.Contains(path, "/shares"):
		return Requirement{Resource: "drive.share", Write: write}, true
	case strings.Contains(path, "/move"):
		return Requirement{Resource: "drive.move", Write: true}, true
	case strings.Contains(path, "/copy"):
		return Requirement{Resource: "drive.copy", Write: true}, true
	case strings.Contains(path, "/rename"):
		return Requirement{Resource: "drive.rename", Write: true}, true
	case strings.Contains(path, "/files/") || strings.Contains(path, "/folders/"):
		if write {
			return Requirement{Resource: "drive.upload", Write: true, ScopeHint: ScopeDrivePathFromURL}, true
		}
		return Requirement{
			Resource:     "drive.folders",
			Alternatives: []string{"drive.files"},
			Write:        false,
			ScopeHint:    ScopeDrivePathFromURL,
		}, true
	case strings.Contains(path, "/search"),
		strings.Contains(path, "/recent"),
		strings.Contains(path, "/starred"),
		strings.Contains(path, "/shared"),
		strings.Contains(path, "/filter-corpus"),
		strings.Contains(path, "/quota"),
		strings.Contains(path, "/trash"):
		if write {
			return Requirement{Resource: "drive.upload", Write: true}, true
		}
		return Requirement{Resource: "drive.files", Write: false}, true
	default:
		return Requirement{}, false
	}
}

func searchRequirement(typesQuery string) (Requirement, bool) {
	types := parseSearchTypes(typesQuery)
	if len(types) == 0 {
		return Requirement{Resource: "mail.search", Write: false, ScopeHint: ScopeMailAccountQuery}, true
	}
	req := Requirement{Write: false, ScopeHint: ScopeMailAccountQuery}
	for _, t := range types {
		switch t {
		case "mail":
			req.Resource = "mail.search"
		case "contacts":
			req.Resource = "contacts.search"
		case "drive":
			req.Resource = "drive.files"
		default:
			continue
		}
		return req, true
	}
	return Requirement{Resource: "mail.search", Write: false, ScopeHint: ScopeMailAccountQuery}, true
}

func parseSearchTypes(raw string) []string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(strings.ToLower(part))
		if part != "" {
			out = append(out, part)
		}
	}
	return out
}

func AllowsRequirement(auth *AuthContext, req Requirement) bool {
	if auth == nil {
		return true
	}
	if req.Resource == "automation.api_tokens" && !req.Write {
		return HasPermission(auth, req.Resource, true)
	}
	if HasPermission(auth, req.Resource, req.Write) {
		return true
	}
	for _, alt := range req.Alternatives {
		if HasPermission(auth, alt, req.Write) {
			return true
		}
	}
	return false
}

func SearchRequirements(typesQuery string) []Requirement {
	types := parseSearchTypes(typesQuery)
	if len(types) == 0 {
		return []Requirement{{Resource: "mail.search", Write: false, ScopeHint: ScopeMailAccountQuery}}
	}
	reqs := make([]Requirement, 0, len(types))
	for _, t := range types {
		switch t {
		case "mail":
			reqs = append(reqs, Requirement{Resource: "mail.search", Write: false, ScopeHint: ScopeMailAccountQuery})
		case "contacts":
			reqs = append(reqs, Requirement{Resource: "contacts.search", Write: false})
		case "drive":
			reqs = append(reqs, Requirement{Resource: "drive.files", Write: false})
		}
	}
	if len(reqs) == 0 {
		return []Requirement{{Resource: "mail.search", Write: false, ScopeHint: ScopeMailAccountQuery}}
	}
	return reqs
}

func ExtractMailAccountID(path, queryAccountID string) string {
	if id := strings.TrimSpace(queryAccountID); id != "" {
		return id
	}
	parts := strings.Split(strings.Trim(path, "/"), "/")
	for i := 0; i < len(parts)-1; i++ {
		if parts[i] == "accounts" && i+1 < len(parts) {
			return parts[i+1]
		}
	}
	return ""
}

func ExtractDrivePathFromURL(fullPath string) string {
	markers := []string{
		"/api/v1/drive/files/",
		"/api/v1/drive/download/",
		"/api/v1/drive/preview/",
		"/api/v1/drive/folders/",
	}
	for _, marker := range markers {
		if idx := strings.Index(fullPath, marker); idx >= 0 {
			rest := fullPath[idx+len(marker):]
			if rest == "" {
				return "/"
			}
			return NormalizeDriveScopePath("/" + rest)
		}
	}
	return ""
}
