package ods

import (
	"archive/zip"
	"bytes"
	"dev.tandem.ws/tandem/camper/pkg/auth"
	"dev.tandem.ws/tandem/camper/pkg/template"
	"encoding/xml"
	"fmt"
	"net/http"
	"strings"
	"time"

	"dev.tandem.ws/tandem/camper/pkg/database"
	"dev.tandem.ws/tandem/camper/pkg/locale"
)

const (
	mimetype               = "application/vnd.oasis.opendocument.spreadsheet"
	metaDashInfManifestXml = `<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest
    xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
    manifest:version="1.3">
  <manifest:file-entry manifest:full-path="/" manifest:version="1.3" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
  <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
  <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
  <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
</manifest:manifest>
`
	metaXml = `<?xml version="1.0" encoding="UTF-8"?>
<office:document-meta
    xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    office:version="1.3">
  <office:meta>
    <meta:creation-date></meta:creation-date>
    <meta:generator>Camper</meta:generator>
  </office:meta>
</office:document-meta>
`
	stylesXml = `<?xml version="1.0" encoding="UTF-8"?>
<office:document-styles
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    office:version="1.3">
</office:document-styles>
`
)

func WriteTable[K interface{}](rows []*K, columns []string, locale *locale.Locale, writeRow func(*strings.Builder, *K) error) ([]byte, error) {
	var sb strings.Builder

	sb.WriteString(`<?xml version="1.0" encoding="UTF-8"?>
<office:document-content
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
    xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"
    xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
    xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
    xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
    xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
    xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
    office:version="1.3">
  <office:scripts/>
  <office:font-face-decls>
    <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
  </office:font-face-decls>
  <office:automatic-styles>
    <style:style style:name="co1" style:family="table-column">
      <style:table-column-properties fo:break-before="auto" style:column-width="0.889in"/>
    </style:style>
    <style:style style:name="ro1" style:family="table-row">
      <style:table-row-properties style:row-height="0.178in" fo:break-before="auto" style:use-optimal-row-height="true"/>
    </style:style>
    <style:style style:name="ta1" style:family="table" style:master-page-name="Default">
      <style:table-properties table:display="true" style:writing-mode="lr-tb"/>
    </style:style>
    <number:date-style style:name="N37" number:automatic-order="true">
      <number:day number:style="long"/>
      <number:text>/</number:text>
      <number:month number:style="long"/>
      <number:text>/</number:text>
      <number:year/>
    </number:date-style>
    <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/>
  </office:automatic-styles>
  <office:body>
    <office:spreadsheet>
      <table:calculation-settings table:automatic-find-labels="false" table:use-regular-expressions="false" table:use-wildcards="true"/>
      <table:table table:name="Sheet1" table:style-name="ta1">
`)
	sb.WriteString(fmt.Sprintf("        <table:table-column table:style-name=\"co1\" table:number-columns-repeated=\"%d\" table:default-cell-style-name=\"Default\"/>\n", len(columns)))
	sb.WriteString(`        <table:table-row table:style-name="ro1">
`)
	for _, t := range columns {
		if err := WriteCellString(&sb, locale.GetC(t, "header")); err != nil {
			return nil, err
		}
	}
	sb.WriteString("        </table:table-row>\n")
	for _, row := range rows {
		sb.WriteString("        <table:table-row table:style-name=\"ro1\">\n")
		if err := writeRow(&sb, row); err != nil {
			return nil, err
		}
		sb.WriteString("        </table:table-row>\n")
	}
	sb.WriteString(`      </table:table>
      <table:named-expressions/>
    </office:spreadsheet>
  </office:body>
</office:document-content>
`)
	return writeOds(sb.String())
}

func writeOds(content string) ([]byte, error) {
	buf := new(bytes.Buffer)
	ods := zip.NewWriter(buf)
	if err := writeOdsFile(ods, "mimetype", mimetype, zip.Store); err != nil {
		return nil, err
	}
	if err := writeOdsFile(ods, "META-INF/manifest.xml", metaDashInfManifestXml, zip.Deflate); err != nil {
		return nil, err
	}
	if err := writeOdsFile(ods, "meta.xml", metaXml, zip.Deflate); err != nil {
		return nil, err
	}
	if err := writeOdsFile(ods, "styles.xml", stylesXml, zip.Deflate); err != nil {
		return nil, err
	}
	if err := writeOdsFile(ods, "content.xml", content, zip.Deflate); err != nil {
		return nil, err
	}
	if err := ods.Close(); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func writeOdsFile(ods *zip.Writer, name string, content string, method uint16) error {
	f, err := ods.CreateHeader(&zip.FileHeader{
		Name:     name,
		Method:   method,
		Modified: time.Now(),
	})
	if err != nil {
		return err
	}
	_, err = f.Write([]byte(content))
	return err
}

func WriteCellString(sb *strings.Builder, s string) error {
	sb.WriteString(`          <table:table-cell office:value-type="string" calcext:value-type="string"><text:p>`)
	if err := xml.EscapeText(sb, []byte(s)); err != nil {
		return err
	}
	sb.WriteString("</text:p></table:table-cell>\n")
	return nil
}

func WriteCellDate(sb *strings.Builder, t time.Time) {
	sb.WriteString(fmt.Sprintf("          <table:table-cell table:style-name=\"ce1\" office:value-type=\"date\" office:date-value=\"%s\" calcext:value-type=\"date\"><text:p>%s</text:p></table:table-cell>\n", t.Format(database.ISODateFormat), t.Format("02/01/06")))
}

func WriteCellFloat(sb *strings.Builder, s string, company *auth.Company, locale *locale.Locale) {
	sb.WriteString(fmt.Sprintf("          <table:table-cell office:value-type=\"float\" office:value=\"%s\" calcext:value-type=\"float\"><text:p>%s</text:p></table:table-cell>\n", s, template.FormatPrice(s, locale.Language, locale.CurrencyPattern, company.DecimalDigits, company.CurrencySymbol)))
}

func MustWriteResponse(w http.ResponseWriter, ods []byte, filename string) {
	w.Header().Set("Content-Type", "application/vnd.oasis.opendocument.spreadsheet")
	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
	w.WriteHeader(http.StatusOK)
	if _, err := w.Write(ods); err != nil {
		panic(err)
	}
}
